创建多交易品种、多周期指标·综合运用
- 跨周期指标缓冲区的状态判定接口
- 指标对象的只读访问器
- 指标对象的存取接口与类型判别
- 指标对象的比较逻辑怎么写
- 多周期指标对象的等值判定与定时器起手
- 跨周期指标的资源回收与定时器保活
- 指标句柄创建与失败重试的实写
- 缓冲区扩容与初始化的容错写法
- 多周期指标里的缓冲区重设与初始化值管理
- 缓冲区偏移与同步校验的底层写法
- 周末零 bars 下的历史根数推算
- 缓冲同步失败的两种退出路径
- 多周期指标缓冲区的失败兜底与回填
- 跨周期缓冲填充的边界处理
- 跨周期缓冲填充的分支处理
- 跨周期取数时索引映射的坑
- 用三段缓冲判读指标线转折
- 跨周期取线状态时的柱索引映射
- 用三根K线位置判定指标线状态
- 用两柱偏移判定指标线与参考位的相对状态
- 指标线相对层级的六种状态判定
- 类别与缓冲区描述方法的实现细节
- 缓冲区序列标志与数据量的读取接口
- 从基类派生指标类的构造套路
- 指标类构造时的参数与描述落地
- A/D 与 ADX 指标类的封装骨架
- ADX 与 ADXW 多周期封装的缓冲区约定
- ADX 与鳄口指标的封装细节
- 封装鳄鱼指标的参数守卫
- 给鳄鱼指标灌入参数与缓冲描述
- AMA 多周期封装里的参数兜底逻辑
- 在 CIndMSTF 派生类里固化指标身份
- 给 ATR 与 Bears Power 塞进参数和描述
- 牛力指标类的参数初始化写法
- 布林带类的参数装配细节
- 布林带与CCI封装里的参数落点
- 给 CCI 包装类塞参数与描述
- 蔡金振荡器构造里的参数兜底逻辑
- 封装 DEMA 指标类的构造逻辑
- DeMarker 指标类的封装与参数兜底
- 封装包络线指标的类构造逻辑
- Envelopes 与 Force Index 的初始化差异
- 在类里给指标挂上身份与缓冲说明
- 分形自适应均线的参数装配
- 用类封装给 FRAMA 和 Gator 打上身份标签
- 给鳄鱼指标灌入参数数组
- Gator 与 Ichimoku 的指标类初始化细节
- 一目均衡表参数的兜底与缓冲区命名
- 在自定义类里挂上 BW MFI 与 Momentum 的构造逻辑
- 动量类指标的参数装配与容错
- MFI 与 MA 指标类的参数装配细节
- 在 CIndMSTF 里塞入 MA 的四参数结构
- OSMA 指标类的构造与参数兜底
- MACD 封装里的参数兜底逻辑
- 给 MACD 与 OBV 指标类补齐参数描述
- 给指标类补上身份与参数外壳
- 给 SAR 与 RSI 封装多周期描述
- 给 RVI 指标类补齐参数与双线缓冲描述
- StdDev 构造里的参数兜底逻辑
- StdDev 与 Stochastic 指标类的参数装配
- 给随机指标封装参数结构体
- 封装 TEMA 指标类的构造细节
- TRIX 封装类的参数兜底与描述生成
- 给 WPR 指标类写构造与参数装配
- 把 VIDYA 封装成多周期子类
- 给自定义指标挂上身份标签
- 自定义指标封装时的参数数组坑点
- 非当前图表的参数标注逻辑
- 用指针集合管住多周期指标
- 指标缓冲区与跨周期取数接口
- 用 CiCustom 接口反查指标内部状态
- 指标集合类的接口骨架
- 指标挂载接口的默认参数设定
- 往指标池里塞布林带、CCI 与 Chaikin 的接口
- 指标句柄注册函数的参数默认值
- 向指标容器追加分形与鳄鱼族系
- 指标批量挂载的函数接口细节
- 给指标容器挂新实例的三个入口
- 指标句柄的批量注册接口
- 指标挂载接口的默认参数写法
- 指标挂载接口的默认参数写法
- 多周期指标容器的定时器与创建回收
- 用句柄反查多周期指标对象并批量算缓冲
- 跨周期取数接口的两种写法
- 给多周期指标绘图缓冲贴标签与偏移
- 从句柄取缓冲区的初始化值与线条状态
- 用句柄取指标线状态与相对比值
- 用句柄给指标对象批量写属性
- 用句柄回查指标缓冲区的时序与同步状态
- 用句柄反查指标对象的几个存取接口
- 用句柄反查指标对象的属性接口
- 从句柄反查指标对象的几个取数接口
- 指标去重与句柄返回的内部逻辑
- 累积能量与趋势强度指标的封装入口
- 鳄鱼与 AMA 指标对象的封装入列
- 多指标对象的统一托管入口
- 把牛力、布林与CCI挂进指标容器
- Chaikin 与 DEMA 的封装注册写法
- 把 DeMarker、Envelopes、Force 塞进指标容器
- 给指标集合挂上 Force、分形与 FrAMA
- 把鳄鱼与一目均衡塞进指标池
- 把云图、BW MFI 与动量塞进指标容器
- 把 MFI、MA、OsMA 塞进指标集合的写法
- OsMA、MACD 与 OBV 的封装式挂载
- 把 SAR、RSI、RVI 塞进指标容器
- 把标准差与随机指标塞进指标池
- 把 TEMA、TriX 与 WPR 塞进指标容器
- 把 WPR、VIDyA 和成交量塞进指标容器
- 往指标池里塞自定义指标的底层入口
- 用双缓冲验证多周期均线类
- 指标骨架里的三个空回调
- 把多类均线塞进一个指标窗口
- 双均线句柄与面板缓冲的初始化细节
- 双均线句柄与面板绘制的初始化落点
- 初始化收尾与逐根计算的分支处理
- 把指标缓冲和鼠标坐标接进面板
- 把指定 K 线的裸数据铺到面板表格
- 把双均线状态铺进自定义面板
- 双均线相对位置的面板实时标注
- 把任意指标改成多品种多周期版的路还长
跨周期指标缓冲区的状态判定接口
在 MT5 里做多周期联动分析时,最怕指标句柄悄悄失效却没人报信。下面这组类方法把「缓冲区相对状态」的判定和配置收拢到一个接口里,能直接拿来判断某根 K 线、某个 buffer 是否处于可计算状态。 核心入口是 BufferLineStateRelative(),它接收源品种 symbol_from、周期 timeframes_from、buffer 序号 buffer_num、偏移 index,以及两个参考价位 level0 与可选 level1(默认 EMPTY_VALUE)。函数返回 ENUM_LINE_STATE 枚举,同时类内通过 IsSuccess() 和 TypeError() 把成功标志与错误类型拆开暴露——前者回传 m_success 布尔,后者回传 m_type_err 枚举,方便上层分别处理。 历史数据不同步是跨周期调用的常见坑。IsSynchronized() 内部调用 SeriesInfoInteger(m_symbol,m_timeframe,SERIES_SYNCHRONIZED),返回该品种该周期是否已同步;若返回 false,贸然读 buffer 值可能拿到 0 或旧数据,外汇与贵金属市场跳空时这种偏差会被放大,属高风险场景。 SetAsSeriesOff() 用于关掉「缓冲区按时间序列倒序索引」的默认行为,配合 IsSeries(buffer_num) 可确认某个 buffer 当前的索引方向。SetID、SetDigits、SetDescription、SetBufferDescription 则负责给实例打标识、定精度、写说明,其中 SetDigits 把 uint 强转 int 存进 m_digits,调用时注意传参别超 int 上限。
ENUM_LINE_STATE BufferLineStateRelative(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE); class=class="str">"cmt">//--- Returns(class="num">1) success flag, (class="num">2) calculation error type class="type">bool IsSuccess(class="type">void) const { class="kw">return this.m_success; } ENUM_ERR_TYPE TypeError(class="type">void) const { class="kw">return this.m_type_err; } class=class="str">"cmt">//--- Sets(class="num">1) identifier, (class="num">2) Digits, (class="num">3) custom description, (class="num">4) description of the specified buffer class="type">void SetID(const class="type">int id) { this.m_id=id; } class="type">void SetDigits(const class="type">uint digits) { this.m_digits=(class="type">int)digits; } class="type">void SetDescription(const class="type">class="kw">string descr) { this.m_description=descr; } class="type">void SetBufferDescription(const class="type">uint buffer_num,const class="type">class="kw">string descr); class=class="str">"cmt">//--- Sets the indexing of buffer arrays of the calculation part not as in the timeseries class="type">void SetAsSeriesOff(class="type">void); class=class="str">"cmt">//--- Returns flag of whether the buffer is set as series, (class="num">2) historical data for symbol/period is synchronized class="type">bool IsSeries(const class="type">uint buffer_num) const; class="type">bool IsSynchronized(class="type">void) const { class="kw">return (class="type">bool)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED); } class=class="str">"cmt">//--- Returns(class="num">1) timeframe, (class="num">2) symbol, (class="num">3) name, (class="num">4) list of parameters, (class="num">5) handle, (class="num">6) Digits class=class="str">"cmt">//--- number of(class="num">7) buffers, (class="num">8) bars, (class="num">9) identifier, (class="num">10) description, (class="num">11) title, (class="num">12) category, class=class="str">"cmt">//--- (class="num">13) number of parameters, (class="num">14) program type, description of(class="num">15) category, (class="num">16) indicator buffer
◍ 指标对象的只读访问器
在封装自定义指标类时,把内部状态藏进私有成员,再暴露一组 const 访问器是常见做法。下面这段就定义了从周期、品种到句柄、精度等 12 个只读接口,外部只能取不能改。 ENUM_TIMEFRAMES Timeframe(void) const 返回 m_timeframe,即指标挂载的周期;string Symbol(void) const 返回 m_symbol,标明对应交易品种。Name、Parameters、Description、Title 分别吐出指标名、参数字符串、描述与窗口标题,方便在面板里做动态标识。 Handle 返回 m_handle(指标句柄,int),Digits 返回 m_digits(价格小数位),BuffersTotal 返回 m_buffers.Size()(缓冲数组元素数,uint),RatesTotal 返回 m_rates_total(可用 K 线数)。ID 返回 m_id 作为实例编号。 开 MT5 把这套访问器塞进你的 CIIndicator 基类,编译后通过 Watch 窗口盯 m_handle 与 m_rates_total,能直接确认指标加载是否成功、数据长度是否够算。外汇与贵金属杠杆高,指标句柄失效时务必判空再 CopyBuffer,避免异常平仓。
ENUM_TIMEFRAMES Timeframe(class="type">void) const { class="kw">return this.m_timeframe; } class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.m_symbol; } class="type">class="kw">string Name(class="type">void) const { class="kw">return this.m_name; } class="type">class="kw">string Parameters(class="type">void) const { class="kw">return this.m_parameters; } class="type">int Handle(class="type">void) const { class="kw">return this.m_handle; } class="type">int Digits(class="type">void) const { class="kw">return this.m_digits; } class="type">uint BuffersTotal(class="type">void) const { class="kw">return this.m_buffers.Size(); } class="type">uint RatesTotal(class="type">void) const { class="kw">return this.m_rates_total; } class="type">int ID(class="type">void) const { class="kw">return this.m_id; } class="type">class="kw">string Description(class="type">void) const { class="kw">return this.m_description; } class="type">class="kw">string Title(class="type">void) const { class="kw">return this.m_title; }
「指标对象的存取接口与类型判别」
这段 CIndMSTF 类的公开方法,把指标运行时的关键元数据都包成了一行式访问器。Category() 返回 m_category,标识指标所属类别;ParamsTotal() 调用 m_param.Size() 拿到参数总个数,实测一个三参数均线指标这里会返回 3。
Program() 给出 ENUM_PROGRAM_TYPE,配合 IsIndicator() 里 Program()==PROGRAM_INDICATOR 的判定,可以在运行时确认对象是否真是指标实例,避免把脚本句柄误当指标用。
TimeframeDescription() 用了 EnumToString(m_timeframe) 再 StringSubstr(...,7) 截掉前缀,比如 PERIOD_M15 会吐出 'M15' 这种短串,直接塞进面板更省地方。Calculated() 转发 BarsCalculated(m_handle),返回已计算 BAR 数——若返回 -1 说明句柄失效,外汇或贵金属品种上这种失效概率随报价中断而升高,属高风险环境常见现象。
Compare() 是虚方法,把传入的 CObject* 强转为 CIndMSTF* 后按 mode 分支比较,方便把多个指标对象丢进 CArray 做排序或查重。
ENUM_IND_CATEGORY Category(class="type">void) const { class="kw">return this.m_category; } class="type">uint ParamsTotal(class="type">void) const { class="kw">return this.m_param.Size(); } ENUM_PROGRAM_TYPE Program(class="type">void) const { class="kw">return this.m_program; } class="type">class="kw">string CategoryDescription(class="type">void); class="type">class="kw">string BufferDescription(const class="type">uint buffer_num); class=class="str">"cmt">//--- Returns(class="num">1) structure of parameters by index from array, (class="num">2) flag of indicator program, (class="num">3) timeframe description class="type">MqlParam GetMqlParam(const class="type">int index) const { class="kw">return this.m_param[index]; } class="type">bool IsIndicator() const { class="kw">return(this.Program()==PROGRAM_INDICATOR); } class="type">class="kw">string TimeframeDescription(class="type">void) const { class="kw">return ::StringSubstr(::EnumToString(this.m_timeframe),class="num">7); } class=class="str">"cmt">//--- Returns amount of calculated data class="type">int Calculated(class="type">void) const { class="kw">return ::BarsCalculated(this.m_handle); } class=class="str">"cmt">//--- Virtual method returning the type of object(indicator) class="kw">virtual class="type">int Type(class="type">void) const { class="kw">return this.m_type; } class=class="str">"cmt">//--- Virtual method for comparing two objects class="kw">virtual class="type">int Compare(const CObject *node,const class="type">int mode=class="num">0) const { const CIndMSTF *compared=node; class="kw">switch(mode)
指标对象的比较逻辑怎么写
在 MT5 自建指标管理类时,经常需要判断两个指标实例谁先谁后、或者是否同一配置。下面这段 switch 用枚举 COMPARE_MODE_XXX 决定按哪种字段排序或比对,返回 1 / -1 / 0 三态,和 C 语言标准库里的 strcmp 约定一致。 按 ID、Handle、Category、Symbol、Timeframe、Description 六种模式比较时,都走「大于返回1、小于返回-1、相等返回0」的短路三元表达式;若落入 default,则不再比单项,而是调 IsEqualIndicators 看全部参数是否相等,不等就统返回 -1。 IsEqualIndicators 先卡两道硬门槛:类型不同、或参数总数(ParamsTotal)不同,直接返回 false,避免后续逐个参数比对的无效开销。外汇与贵金属指标多周期加载时,这类比较若写错可能引发 EA 重复挂单,实盘前务必在策略测试器里跑一遍。 让小布替你跑这套 把下面代码贴进 MT5 的 MQEditor,建个 CIndMSTF 派生类,用 COMPARE_MODE_SYMBOL 比 EURUSD 与 XAUUSD 两个实例,观察返回的 -1 是否符合预期。
{
case COMPARE_MODE_ID : class="kw">return(this.ID()>compared.ID() ? class="num">1 : this.ID()<compared.ID() ? -class="num">1 : class="num">0);
case COMPARE_MODE_HANDLE : class="kw">return(this.Handle()>compared.Handle() ? class="num">1 : this.Handle()<compared.Handle() ? -class="num">1 : class="num">0);
case COMPARE_MODE_CATEGORY : class="kw">return(this.Category()>compared.Category() ? class="num">1 : this.Category()<compared.Category() ? -class="num">1 : class="num">0);
case COMPARE_MODE_SYMBOL : class="kw">return(this.Symbol()>compared.Symbol() ? class="num">1 : this.Symbol()<compared.Symbol() ? -class="num">1 : class="num">0);
case COMPARE_MODE_TIMEFRAME : class="kw">return(this.Timeframe()>compared.Timeframe() ? class="num">1 : this.Timeframe()<compared.Timeframe() ? -class="num">1 : class="num">0);
case COMPARE_MODE_DESCRIPTION : class="kw">return(this.Description()>compared.Description() ? class="num">1 : this.Description()<compared.Description() ? -class="num">1 : class="num">0);
class=class="str">"cmt">//--- Equality of all object parameters
class="kw">default : class="kw">return(this.IsEqualIndicators(compared) ? class="num">0 : -class="num">1);
}
class=class="str">"cmt">//--- Returns the flag of equality of parameters of two indicator objects
class="type">bool IsEqualIndicators(const CIndMSTF *compared) const
{
if(this.Type()!=compared.Type() || this.ParamsTotal()!=compared.ParamsTotal())
class="kw">return false;◍ 多周期指标对象的等值判定与定时器起手
在封装多周期指标类时,比较两个实例是否指向同一标的与周期是关键一步。下面这段代码用按位与短路累积 res,依次比对全部参数、时间帧和交易品种,任一不等就整体返回 false。 bool res=true; int total=(int)this.ParamsTotal(); for(int i=0;i<total;i++) res &=this.IsEqualParameters(this.m_param[i],compared.GetMqlParam(i)); res &=(this.Timeframe()==compared.Timeframe()); res &=(this.Symbol()==compared.Symbol()); return res; 构造函数里用 EventSetTimer(1) 拉起 1 秒周期的 OnTimer 回调,若失败会打印错误码。注意 m_symbol 在传入空串时回退到当前 Symbol(),m_timeframe 在 PERIOD_CURRENT 时取 Period(),这两个默认值能省掉调用方重复传参。 ArrayResize(this.m_buffers,buffers) 按缓冲区数量扩数组,返回值不等于 buffers 说明分配失败,此时应查 GetLastError()。外汇与贵金属杠杆高,这类底层封装若缓冲区错位,可能让 EA 误读跨周期信号,上 MT5 跑一遍构造与比较逻辑再接实盘。
class="type">bool res=true; class="type">int total=(class="type">int)this.ParamsTotal(); for(class="type">int i=class="num">0;i<total;i++) res &=this.IsEqualParameters(this.m_param[i],compared.GetMqlParam(i)); res &=(this.Timeframe()==compared.Timeframe()); res &=(this.Symbol()==compared.Symbol()); class="kw">return res; class="type">void OnTimer(class="type">void); CIndMSTF(){} CIndMSTF(const ENUM_INDICATOR type,const class="type">uint buffers,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe); ~CIndMSTF(); CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const class="type">uint buffers,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) { ::ResetLastError(); if(!::EventSetTimer(class="num">1)) ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError()); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_type=type; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_handle=INVALID_HANDLE; this.m_digits=::Digits(); this.m_success=true; this.m_type_err=ERR_TYPE_NO_ERROR; ::ResetLastError(); if(::ArrayResize(this.m_buffers,buffers)!=buffers) ::PrintFormat("%s: Buffers ArrayResize failed. Error %lu"__FUNCTION__,::GetLastError()); }
「跨周期指标的资源回收与定时器保活」
在 MT5 里写多周期(MSTF)指标类,构造收尾和后台保活是两道容易漏的工序。析构时若只置空句柄,定时器仍可能挂在客户端事件循环里,必须显式 EventKillTimer() 再释放指标句柄,否则切换品种或卸载 EA 时可能留下幽灵定时器。 下面这段析构逻辑把缓冲区数组逐个 ArrayFree,并用 IndicatorRelease 交还句柄给终端;若释放失败会打印函数名、标题与错误码,方便在专家日志里定位是哪一层没退干净。
CIndMSTF::~CIndMSTF() { class=class="str">"cmt">//--- Delete timer ::EventKillTimer(); class=class="str">"cmt">//--- Release indicator handle ::ResetLastError(); if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle)) ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError()); class=class="str">"cmt">//--- Free up the memory of buffer arrays for(class="type">int i=class="num">0;i<(class="type">int)this.BuffersTotal();i++) ::ArrayFree(this.m_buffers[i].array); }
class="type">void CIndMSTF::OnTimer(class="type">void) { if(this.Symbol()==::Symbol() && this.Timeframe()==::Period()) class="kw">return; class="kw">static class="type">int count1=class="num">0; class="kw">static class="type">int count2=class="num">0; if(count1>=TIMER_COUNT_1) { class="type">class="kw">datetime array[class="num">1]; ::CopyTime(this.m_symbol,this.m_timeframe,class="num">0,class="num">1,array); count1=class="num">0; } if(count2>=TIMER_COUNT_2) { class="kw">static class="type">int count=class="num">0;
CIndMSTF::~CIndMSTF() { class=class="str">"cmt">//--- Delete timer ::EventKillTimer(); class=class="str">"cmt">//--- Release indicator handle ::ResetLastError(); if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle)) ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError()); class=class="str">"cmt">//--- Free up the memory of buffer arrays for(class="type">int i=class="num">0;i<(class="type">int)this.BuffersTotal();i++) ::ArrayFree(this.m_buffers[i].array); } class="type">void CIndMSTF::OnTimer(class="type">void) { if(this.Symbol()==::Symbol() && this.Timeframe()==::Period()) class="kw">return; class="kw">static class="type">int count1=class="num">0; class="kw">static class="type">int count2=class="num">0; if(count1>=TIMER_COUNT_1) { class="type">class="kw">datetime array[class="num">1]; ::CopyTime(this.m_symbol,this.m_timeframe,class="num">0,class="num">1,array); count1=class="num">0; } if(count2>=TIMER_COUNT_2) { class="kw">static class="type">int count=class="num">0;
指标句柄创建与失败重试的实写
在 MT5 自定义指标类里,若上一次计算没跑通,靠 ChartSetSymbolPeriod(0,Symbol(),Period()) 强行触发一次图表刷新来模拟 tick,是低成本自救手段。下面这段代码把重试次数卡在 3 次以内:前两次只记 log 并累加 count,超过 2 次直接把 count 归零、m_success 置 true,避免无限阻塞定时器。
class=class="str">"cmt">//--- 若上一次指标计算失败,模拟一个 tick 并打印日志 if(!this.m_success) { if(::ChartSetSymbolPeriod(class="num">0,::Symbol(),::Period())) { count++; ::PrintFormat("%s::%s: Tick emulation. Attempt %ld of class="num">3 ...",__FUNCTION__,this.Title(),count); if(count>class="num">2) { count=class="num">0; this.m_success=true; } } } class=class="str">"cmt">//--- 重置计数器 count2=class="num">0; } class=class="str">"cmt">//--- 增加定时器计数 count1++; count2++; }
class="type">int CIndMSTF::CreateIndicator(class="type">void) { class="type">class="kw">string name=::StringFormat("%s(%s,%s)",::StringSubstr(::EnumToString(this.m_type),class="num">4),this.m_symbol,this.TimeframeDescription()); ::ResetLastError(); this.m_handle=::IndicatorCreate(this.m_symbol,this.m_timeframe,this.m_type,this.m_param.Size(),this.m_param); if(this.m_handle==INVALID_HANDLE) ::PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,name,::GetLastError()); else this.SetAsSeriesOff(); class="kw">return this.m_handle; }
if(!this.m_success) { if(::ChartSetSymbolPeriod(class="num">0,::Symbol(),::Period())) { count++; ::PrintFormat("%s::%s: Tick emulation. Attempt %ld of class="num">3 ...",__FUNCTION__,this.Title(),count); if(count>class="num">2) { count=class="num">0; this.m_success=true; } } } count2=class="num">0; } count1++; count2++; } class="type">int CIndMSTF::CreateIndicator(class="type">void) { class="type">class="kw">string name=::StringFormat("%s(%s,%s)",::StringSubstr(::EnumToString(this.m_type),class="num">4),this.m_symbol,this.TimeframeDescription()); ::ResetLastError(); this.m_handle=::IndicatorCreate(this.m_symbol,this.m_timeframe,this.m_type,this.m_param.Size(),this.m_param); if(this.m_handle==INVALID_HANDLE) ::PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,name,::GetLastError()); else this.SetAsSeriesOff(); class="kw">return this.m_handle; } class="type">bool CIndMSTF::BufferResize(const class="type">uint buffer_num,const class="type">int new_buff_size) { if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
◍ 缓冲区扩容与初始化的容错写法
在自定义多周期指标类里,单个缓冲区的重设大小必须先校验编号。若传入的 buffer_num 超过 BuffersTotal()-1,方法会向日志打印形如 CIndMSTF: Invalid buffer number passed (3). Value must be 0 - 2 的报错并返回 false,避免越界写入导致 MT5 终端崩溃。
单缓冲区重设走 BufferResize(buffer_num, new_buff_size),内部调用 BuffResize 前会用 ResetLastError() 清掉旧错误码;失败则通过 GetLastError() 把具体错误号带进日志,方便在策略测试器里定位是 4003(数组越界)还是 4004(数组大小无效)。
批量处理时,BuffersResize 用 res &= this.m_buffers[i].BuffResize(new_buff_size) 做按位与累积——只要有一个缓冲区重设失败,整体就返回 false。注意 &= 的短路特性在这里失效,循环会跑满 total 次,所以日志可能一口气刷出多条失败记录。
初始化方法 BufferInitialize 在重设成功后调 InitBuffer() 填预设初值;BuffersInitialize 则准备对全部缓冲区循环,但原片段在 int total=(int)this.BuffersTotal(); 处截断,实际发布时须补上 for 循环与 InitBuffer 调用,否则编译能通过、运行却留出未初始化数组,外汇与贵金属行情跳动下可能读出脏数据,杠杆交易风险偏高,务必在 MT5 里单步验证。
::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return false; } class=class="str">"cmt">//--- Resize the buffer ::ResetLastError(); class="type">bool res=this.m_buffers[buffer_num].BuffResize(new_buff_size); class=class="str">"cmt">//--- If unsuccessful, print a message to the log if(!res) ::PrintFormat("%s::%s: Buffer(%lu) resize failed. Error %lu",__FUNCTION__,this.Title(),buffer_num,::GetLastError()); class=class="str">"cmt">//--- Return the result of resizing the buffer array class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Resize all indicator buffers | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIndMSTF::BuffersResize(const class="type">int new_buff_size) { class=class="str">"cmt">//--- In a loop through all indicator buffers, add to the &class="macro">#x27;res&class="macro">#x27; variable the resizing result of each next buffer class="type">bool res=true; class="type">int total=(class="type">int)this.BuffersTotal(); for(class="type">int i=class="num">0;i<total;i++) res &=this.m_buffers[i].BuffResize(new_buff_size); class=class="str">"cmt">//--- Return the result of resizing all arrays of indicator buffers class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the specified indicator buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIndMSTF::BufferInitialize(const class="type">uint buffer_num,const class="type">int new_buff_size) { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and class="kw">return &class="macro">#x27;false&class="macro">#x27; if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return false; } class=class="str">"cmt">//--- resizing the buffer array class="type">bool res=this.BufferResize(buffer_num,new_buff_size); class=class="str">"cmt">//--- If successfully resized, initialize the buffer with the set initializing value if(res) this.m_buffers[buffer_num].InitBuffer(); class=class="str">"cmt">//--- Return the result class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize all indicator buffers | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIndMSTF::BuffersInitialize(const class="type">int new_buff_size) { class=class="str">"cmt">//--- In a loop through all indicator buffers, add to the &class="macro">#x27;res&class="macro">#x27; variable the resizing result of each next buffer class=class="str">"cmt">//--- If successfully resized, initialize the buffer with the set initializing value class="type">bool res=true; class="type">int total=(class="type">int)this.BuffersTotal();
「多周期指标里的缓冲区重设与初始化值管理」
在自定义多周期指标类里,缓冲区扩容不是只改一个数组大小就完事。下面这段循环会对全部缓冲区调用 BuffResize,只有返回 true 才继续 InitBuffer,任意一次失败都会让整体结果 res 变成 false,从而中断初始化流程。 for(int i=0;i<total;i++) { res &=this.m_buffers[i].BuffResize(new_buff_size); if(res) this.m_buffers[i].InitBuffer(); } //--- Return the overall result return res; } 设置某号缓冲区的初始值靠 SetBufferInitValue。方法先校验 buffer_num 是否越界:超过 BuffersTotal()-1 就打印形如「Invalid buffer number passed (3). Value must be 0 - 7」的日志并直接 return,连赋值都不做。 void CIndMSTF::SetBufferInitValue(const uint buffer_num,const double value) { if(buffer_num>this.BuffersTotal()-1) { string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1)); ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); return; } this.m_buffers[buffer_num].SetInitValue(value); } 读回初始值用 BufferInitValue。同样先做越界检查;若越界且存在缓冲区,会回退返回 0 号缓冲区的初始值,否则给 EMPTY_VALUE。这意味着当你传错序号,拿到的可能不是报错而是某个真实缓冲区的数,排查时容易误判。 double CIndMSTF::BufferInitValue(const uint buffer_num) const { if(buffer_num>this.BuffersTotal()-1) { string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1)); ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); return(this.BuffersTotal()>0 ? this.BufferInitValue(0) : EMPTY_VALUE); } return this.m_buffers[buffer_num].InitValue(); } 偏移量设置 SetBufferShift 的入口校验和上面完全一致:越界时拼出 buff_limit 字符串并打日志后退出,不会改动任何 shift。写 EA 调用前,最好先断言 buffer_num < BuffersTotal(),避免静默失效。外汇与贵金属市场杠杆高、滑点大,这类底层缓冲区错误可能引发信号错位,实盘前务必在 MT5 策略测试器用历史数据跑一遍确认。
for(class="type">int i=class="num">0;i<total;i++) { res &=this.m_buffers[i].BuffResize(new_buff_size); if(res) this.m_buffers[i].InitBuffer(); } class=class="str">"cmt">//--- Return the overall result class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the initializing value for the specified buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CIndMSTF::SetBufferInitValue(const class="type">uint buffer_num,const class="type">class="kw">double value) { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return; } class=class="str">"cmt">//--- Set a new initializing value for the specified buffer this.m_buffers[buffer_num].SetInitValue(value); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the initialization value of the specified buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CIndMSTF::BufferInitValue(const class="type">uint buffer_num) const { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class=class="str">"cmt">//--- If the indicator has buffers, class="kw">return the initializing value of the first one, otherwise EMPTY_VALUE class="kw">return(this.BuffersTotal()>class="num">0 ? this.BufferInitValue(class="num">0) : EMPTY_VALUE); } class=class="str">"cmt">//--- Return the initializing value of the requested buffer class="kw">return this.m_buffers[buffer_num].InitValue(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Sets the offset value for the specified buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CIndMSTF::SetBufferShift(const class="type">uint buffer_num,const class="type">int value) { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1));
缓冲区偏移与同步校验的底层写法
在自定义多周期指标类里,单个缓冲区的水平偏移(shift)不能直接赋值,必须通过封装方法做边界检查。下面这段逻辑先校验 buffer_num 是否越界:若大于 BuffersTotal()-1,就打印含 __FUNCTION__ 和合法区间的日志,单缓冲区时区间字符串为 "0",多缓冲区则为 "0 - N-1",随后直接 return 不执行赋值。 越界读取偏移时也有对称处理:BufferShift() 在编号非法时,若总缓冲区数大于 0 则返回 m_buffers[0].Shift(),否则返回 0。这意味着调用方拿到 0 未必代表无偏移,可能是兜底值,调试时要看日志里的 Invalid buffer number。 Calculate() 的入口先置 m_success=true 与 m_type_err=ERR_TYPE_NO_ERROR,再判 IsSynchronized()。未同步时错误类型改 ERR_TYPE_NO_CYNC 且 m_success &= false,返回 false——此时指标不会跑计算,只等下一 tick。若 Calculated() 返回 WRONG_VALUE(-1)说明刚启动数据下载,返回 0 则说明尚未算完,两者都走 false 分支并打对应日志。外汇与贵金属行情在跳空或重连时同步失败概率偏高,这类防护能避免用空数据画图。
::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return; } class=class="str">"cmt">//--- Set the offset value for the buffer this.m_buffers[buffer_num].SetShift(value); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the offset value of the specified buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CIndMSTF::BufferShift(const class="type">uint buffer_num) const { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class=class="str">"cmt">//--- If the indicator has buffers, class="kw">return the shift value of the very first one, otherwise class="num">0 class="kw">return(this.BuffersTotal()>class="num">0 ? this.m_buffers[class="num">0].Shift() : class="num">0); } class=class="str">"cmt">//--- Return the offset value of the requested buffer class="kw">return this.m_buffers[buffer_num].Shift(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fill object buffers with data from the calculation part buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIndMSTF::Calculate(class="type">void) { class=class="str">"cmt">//--- Set the success flag to true, and the error type to no error this.m_success=true; this.m_type_err=ERR_TYPE_NO_ERROR; class=class="str">"cmt">//--- If the data is not yet synchronized with the trade server, if(!this.IsSynchronized()) { class=class="str">"cmt">//--- Log a message about non-synchronized data, ::PrintFormat("%s::%s: Waiting for data to sync...",__FUNCTION__,this.Title()); class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27; this.m_type_err=ERR_TYPE_NO_CYNC; this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- If the Calculated method returned -class="num">1, this means the start of data downloading if(this.Calculated()==WRONG_VALUE) { class=class="str">"cmt">//--- Log a message about the start of data downloading, ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription()); class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27; this.m_type_err=ERR_TYPE_NO_DATA; this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- If the Calculated method returned class="num">0, this means that the indicator has not yet been calculated if(this.Calculated()==class="num">0) { class=class="str">"cmt">//--- Log a message about waiting for the indicator to be calculated,
◍ 周末零 bars 下的历史根数推算
MT5 里直接调 Bars() 取某品种某周期的数据根数,平时没问题;但周末或会话刚连上时经常返回 0。这时候若直接拿去做指标初始化,会触发 ERR_TYPE_NO_CALC 并置 m_success 为 false,计算直接中断。 代码里的兜底做法是改用 SeriesInfoInteger 取 SERIES_FIRSTDATE 与 SERIES_LASTBAR_DATE,再用 PeriodSeconds 把时间差折算成根数:((lastdate-firstdate)/sec)+1。最后用 fmin 夹住 TerminalInfoInteger(TERMINAL_MAXBARS),避免超出终端上限。 拿到 bars 后还有一道硬门槛——若 m_rates_total 落在 0~2(即不足 3 根),直接报 ERR_TYPE_NO_DATA 退出。也就是说,实盘里黄金 XAUUSD 在周日早上重启 EA,若历史只同步到 2 根以内,你的指标计算块不会跑,只会等下一 tick。外汇与贵金属杠杆高,这类静默等待可能造成信号延迟,开 MT5 用专家日志过滤 ERR_TYPE_NO_DATA 就能复现。
::PrintFormat("%s::%s: Waiting for a new tick and when the indicator will be calculated...",__FUNCTION__,this.Title()); class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27; this.m_type_err=ERR_TYPE_NO_CALC; this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- Get the number of data bars for the indicator symbol/period class="type">int bars=::Bars(this.m_symbol,this.m_timeframe); class=class="str">"cmt">//--- If the Bars function returned a zero value, which often happens on weekends, calculate the available number of bars if(bars==class="num">0) { class=class="str">"cmt">//--- Get the date of the very first available bar in history for the symbol/period class="type">class="kw">datetime firstdate=(class="type">class="kw">datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE); class=class="str">"cmt">//--- Get the date of the last(current) bar in history for the symbol/period class="type">class="kw">datetime lastdate=(class="type">class="kw">datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE); class=class="str">"cmt">//--- Calculate the number of bars between the first and last dates of history class="type">int sec=::PeriodSeconds(this.m_timeframe); class="type">ulong date_bars=(((class="type">ulong)lastdate-(class="type">ulong)firstdate)/(sec>class="num">0 ? sec : class="num">1))+class="num">1; class=class="str">"cmt">//--- Write to the &class="macro">#x27;bars&class="macro">#x27; variable the smaller value of the calculated number of bars and the maximum number of bars available in the terminal bars=(class="type">int)fmin(date_bars,::TerminalInfoInteger(TERMINAL_MAXBARS)); } class=class="str">"cmt">//--- Write the resulting number of available bars to m_rates_total if(this.m_rates_total!=bars) this.m_rates_total=bars; class=class="str">"cmt">//--- If the number of available bars is received, and it is class="num">2 or less, if(this.m_rates_total>=class="num">0 && this.m_rates_total<class="num">3) { class=class="str">"cmt">//--- Log a message about the number of available bars being too small ::PrintFormat("%s::%s: Not enough data for calculation: %ld bars. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_rates_total); class=class="str">"cmt">//--- set the error type, add &class="macro">#x27;false&class="macro">#x27; to the error flag and class="kw">return &class="macro">#x27;false&class="macro">#x27; this.m_type_err=ERR_TYPE_NO_DATA; this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- Calculate the number of bars required to calculate the indicator class=class="str">"cmt">//--- Either the entire available history, or class="num">1 when a new bar opens, or class="num">0 on the current tick this.m_limit=this.m_rates_total-this.m_prev_calculated; this.m_prev_calculated=this.Calculated(); class=class="str">"cmt">//--- Declare an array of size class="num">2 to receive data into it from the indicator&class="macro">#x27;s calculation part buffer class=class="str">"cmt">//--- We always get two bars: previous and current class="type">class="kw">double array[class="num">2]; class=class="str">"cmt">//--- Get the number of indicator buffers class="type">int total=(class="type">int)this.BuffersTotal(); class=class="str">"cmt">//--- If the calculated m_limit is greater than class="num">1, it means either the first launch or changes in historical data
「缓冲同步失败的两种退出路径」
当 m_limit 大于 1 时,说明需要整段重算指标。此时会遍历所有缓冲区,先调用 BufferInitialize 把数组按 m_rates_total 尺寸重置为各缓冲区的空值,再用 CopyBuffer 从指标句柄把历史数据搬进对象缓冲区。 CopyBuffer 的返回值 copied 若不等于 m_rates_total,就代表数据没拿全。copied 为 WRONG_VALUE(-1)通常意味着历史数据还在后台下载,这时打印等待下一 tick 的日志;其他情况则输出「已有数据量 / 实际拷贝量」便于排查。两种情形都会把 m_type_err 标成 ERR_TYPE_NO_DATA,并将 m_success 与 false 做位与后直接 return false,等下一 tick 再试。 若 m_limit 小于等于 1,对应新 K 线开盘(m_limit==1)或当前 tick(m_limit==0),只需算前一根与当前根。新 bar 场景下若 BufferResize 失败,会进入另一处错误处理分支——这部分逻辑决定了重算请求会不会被静默丢弃,开 MT5 跑自定义指标时建议单步跟一下这句的返回。 外汇与贵金属行情在高波动时段可能触发历史数据重下,这类缓冲同步失败属正常概率事件,不代表指标本身有 bug。
if(this.m_limit>class="num">1) { class=class="str">"cmt">//--- In a loop over the number of indicator buffers for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- resize the indicator buffer array and initialize it to the "empty" value set for this buffer this.BufferInitialize(i,this.m_rates_total); ::ResetLastError(); class=class="str">"cmt">//--- Copy all available historical data from indicator&class="macro">#x27;s calculation part array to buffer array of indicator object class="type">int copied=::CopyBuffer(this.m_handle,i,-this.m_buffers[i].Shift(),this.m_rates_total,this.m_buffers[i].array); class=class="str">"cmt">//--- If not all data is copied if(copied!=this.m_rates_total) { class=class="str">"cmt">//--- If CopyBuffer returned -class="num">1, this means the start of historical data downloading class=class="str">"cmt">//--- print a message about this to the log if(copied==WRONG_VALUE) ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription()); class=class="str">"cmt">//--- In any other case, not all data has been copied yet class=class="str">"cmt">//--- print a message about this to the log else ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied); class=class="str">"cmt">//--- Write the absence of data to the error type this.m_type_err=ERR_TYPE_NO_DATA; class=class="str">"cmt">//--- Add &class="macro">#x27;false&class="macro">#x27; to the result and class="kw">return &class="macro">#x27;false&class="macro">#x27; to exit the method and wait for the next tick this.m_success &=false; class="kw">return false; } } class=class="str">"cmt">//--- If we exited the loop of copying all indicator buffers, then everything was successful - class="kw">return &class="macro">#x27;true&class="macro">#x27; class="kw">return true; } class=class="str">"cmt">//--- If calculated m_limit is less than or equal to class="num">1, this means either opening of a new bar(m_limit==class="num">1) or current tick(m_limit==class="num">0) class=class="str">"cmt">//--- In this case, it is necessary to calculate two bars - the first and the current if(this.m_limit<=class="num">1) { class=class="str">"cmt">//--- In a loop over the number of indicator buffers for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- If this is the opening of a new bar and resizing the indicator buffer failed, if(this.m_limit==class="num">1 && !this.BufferResize(i,this.m_rates_total)) {
多周期指标缓冲区的失败兜底与回填
在 CIndMSTF 的缓冲区更新逻辑里,一旦 BufferResize 报错,代码会直接把 m_success 按位与 false 并 return false,错误由 BufferResize 内部打印到日志,调用方拿不到数据但能靠 m_success 判断链路断点。 紧接着的 CopyBuffer 调用要求从指标计算缓冲区精确拷贝 2 根 bar:传入 -this.m_buffers[i].Shift() 作为起始偏移、长度写死为 2,返回值不等于 2 就视为失败。此时用 ResetLastError 清场后再 PrintFormat 打出函数名、指标标题、缓冲区编号和 GetLastError,同样把 m_success 置否并返回。 拷贝成功后的回填动作很直白——array[1] 进 DataTotal(i)-1、array[0] 进 DataTotal(i)-2,也就是把最新两根 bar 倒序写回对象缓冲。这套写法意味着你若在 MT5 里改了 Shift() 逻辑却没同步 DataTotal 的索引,会出现新值覆盖错位,开 MT5 挂个 Print 看这两行下标就能验证。 DataToBuffer 方法则负责把类内缓冲倒给外部数组:先强制 ArraySetAsSeries(buffer,true) 保证时序索引,再用 symbol_to 和 timeframe_to 的空值兜底到当前品种与周期。limit>1 且 m_limit>1 时判定为首启或历史变动,会打印初始化提示,外汇与贵金属多周期回测中这类历史切换很频繁,属于高风险的隐性重算点。
this.m_success &= false; class="kw">return false; ::ResetLastError(); if(::CopyBuffer(this.m_handle,i,-this.m_buffers[i].Shift(),class="num">2,array)!=class="num">2) { ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError()); this.m_success &= false; class="kw">return false; } this.m_buffers[i].array[this.DataTotal(i)-class="num">1]=array[class="num">1]; this.m_buffers[i].array[this.DataTotal(i)-class="num">2]=array[class="num">0]; class="kw">return true; class="kw">return false; class="type">bool CIndMSTF::DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">int limit,class="type">class="kw">double &buffer[]) { this.m_success=true; class="type">bool as_series=::ArrayGetAsSeries(buffer); if(!as_series) ::ArraySetAsSeries(buffer,true); class="type">class="kw">string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to); ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to); class="type">class="kw">datetime array[class="num">2]; if(limit>class="num">1 && this.m_limit>class="num">1) { ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
◍ 跨周期缓冲填充的边界处理
在自定义指标里做跨周期取数,最麻烦的不是算,而是把不同时间框架的柱线对齐到当前图表缓冲。上面这段逻辑先按终端最大柱数 TERMINAL_MAXBARS 与传入 limit 取小,循环上限被钳在 fmin(TerminalInfoInteger(TERMINAL_MAXBARS), limit),当 limit≤1 时直接给 2,避免零长度或单根导致后续 CopyTime 取两根失败。 若图表周期与类对象周期一致,直接 buffer[i]=this.GetData(buffer_num,i) 搬数组,无额外开销;不一致才进 else 分支用 CopyTime 拉当前图表两根时间,再用 iBarShift 反查类对象周期对应的 bar 索引。 这里有两个实战坑:一是 CopyTime 返回不等于 2 时,错误码 4401(终端无数据)选择 continue 跳过,其余错误则把 m_success 置 false 并 return false,说明历史缺口和真错误要分开处理;二是 i>1 才写历史缓冲,零号与一号柱另走分支再 CopyTime 取类对象自身 0、1 号时间,防止用未闭合的当前棒算值造成缓冲抖动。外汇与贵金属跨周期指标在高波动时段可能因报价断层出现 4401,属正常风险。 别把正态当圣经 iBarShift 返回 WRONG_VALUE 时不 return 而是 continue,仅标记 m_success=false,意味着单根对齐失败不会中断整轮填充,但会在调用层留下“部分成功”状态,验证时记得查这个标志而非只看缓冲长度。
::ArrayInitialize(buffer,this.BufferInitValue(buffer_num)); class=class="str">"cmt">//--- Set the value of the loop counter(no more than the maximum number of bars in the terminal on the chart) class="type">int count=(limit<=class="num">1 ? class="num">2 : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit)); class=class="str">"cmt">//--- In a loop from the zero bar to the value of the loop counter for(class="type">int i=class="num">0;i<count;i++) { class=class="str">"cmt">//--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array if(timeframe==::Period() && this.m_timeframe==::Period()) buffer[i]=this.GetData(buffer_num,i); class=class="str">"cmt">//--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object else { class=class="str">"cmt">//--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(symbol,timeframe,i,class="num">2,array)!=class="num">2) { class=class="str">"cmt">//--- If there is no data in the terminal, move on if(::GetLastError()==class="num">4401) class="kw">continue; class=class="str">"cmt">//--- Error in obtaining existing data - class="kw">return false this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- Using time of bar of current chart timeframe, find corresponding index of bar of class object&class="macro">#x27;s chart period ::ResetLastError(); class="type">int bar=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]); if(bar==WRONG_VALUE) { this.m_success &=false; class="kw">continue; } class=class="str">"cmt">//--- If this is historical data(not the first or zero bar) - class=class="str">"cmt">//--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer if(i>class="num">1) buffer[i]=this.GetData(buffer_num,bar); class=class="str">"cmt">//--- If this is the current(zero) or previous(first) bar else { class=class="str">"cmt">//--- Get the time of bars class="num">0 and class="num">1 by symbol/timeframe of the class object if(::CopyTime(this.m_symbol,this.m_timeframe,class="num">0,class="num">2,array)!=class="num">2) { this.m_success &=false;
「跨周期缓冲填充的分支处理」
这段逻辑解决一个实操痛点:当调用指标的图表周期与指标对象自身周期不一致时,如何把数据正确写进目标 buffer。核心靠 iBarShift 把时间映射成两根 K 线的索引,再做分支填充。 若图表周期低于对象周期(例如图表 M1、对象 M5),从上一根 bar 向当前 bar 倒序循环,按是否越过 bar0 位置来取计算缓冲的第 1 或第 0 格数据。这里 j>bar0 时取 index 1,否则取 index 0,保证低周期每根 bar 都能对齐到高周期最近的两根输出。 若图表周期高于对象周期(例如图表 H1、对象 M15),先用 CopyTime 取图表最近 2 根 bar 的时间,返回值不等于 2 就直接置 m_success 为 false 并退出。随后用对象自身的 symbol/timeframe 调 iBarShift 反查对应索引,把计算缓冲的 bar1、bar0 写进 buffer[1]、buffer[0]。 验证方式:在 MT5 建一个多周期指标,故意让主图周期与内部周期差 4 倍以上,打印 buffer[0]、buffer[1] 与 GetData 返回值,若低周期图上出现连续同值段属正常,高周期图则只应有两根有效值。外汇与贵金属跨周期信号受滑点干扰,实际吻合度可能偏低,需自行回测。
class="kw">return false; } class=class="str">"cmt">//--- Using time, get indexes of current and previous bars on the chart whose symbol/period was passed to method class="type">int bar0=::iBarShift(symbol,timeframe,array[class="num">1]); class="type">int bar1=::iBarShift(symbol,timeframe,array[class="num">0]); if(bar0==WRONG_VALUE || bar1==WRONG_VALUE) { this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- If the chart timeframe is lower than the timeframe of the class object, if(timeframe<this.m_timeframe) { class=class="str">"cmt">//--- in a loop from bar with smaller time to current chart bar, fill the buffer with data from the last class="num">2 cells of the indicator buffer array for(class="type">int j=bar1;j>=class="num">0;j--) buffer[j]=this.GetData(buffer_num,(j>bar0 ? class="num">1 : class="num">0)); } class=class="str">"cmt">//--- If the chart timeframe is higher than the timeframe of the class object, else { class=class="str">"cmt">//--- Get the time of the current and previous bars by symbol/timeframe of the current chart if(::CopyTime(symbol,timeframe,class="num">0,class="num">2,array)!=class="num">2) { this.m_success &=false; class="kw">return false; } class=class="str">"cmt">//--- Using time, get indexes of bars in indicator&class="macro">#x27;s calculation part buffer, corresponding to time of current and previous bars on the chart class="type">int bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">1]); class="type">int bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]); class=class="str">"cmt">//--- Write into indicator buffer, at indexes class="num">1 and class="num">0, values from corresponding indexes of calculation part buffer buffer[class="num">1]=this.GetData(buffer_num,bar1); buffer[class="num">0]=this.GetData(buffer_num,bar0); } } } class=class="str">"cmt">//--- Set initial indexing of the buffer array passed to the method
跨周期取数时索引映射的坑
在多周期指标类里,GetData 负责按索引回吐缓冲区数值,但缓冲区默认被 ArraySetAsSeries 设成时间序列,所以代码里用 DataTotal(buffer_num)-1-index 做了一次反向定位。若传入的 buffer_num 超过 BuffersTotal()-1,直接走日志报错并返回该缓冲区的初始化空值,这一步能拦掉大部分越界崩溃。
GetDataTo 更棘手:它要把目标品种/周期上的 bar 索引,映射到当前类实例所绑定的 symbol/timeframe 上。先用 CopyTime 取目标 bar 的开盘时间,再拿 iBarShift 反查本周期对应 bar 序号;任一环节失败(比如返回 WRONG_VALUE)就吐 BufferInitValue。外汇与贵金属跨周期调用时,不同品种流动性差异可能导致 iBarShift 找不到对应 bar,属于高风险操作,实盘前务必在 MT5 用非常规品种对测一遍。
写这类封装时,别迷信索引一一对应。M15 的一根 bar 在 H1 上可能横跨多根,iBarShift 返回的是「时间吻合」的那一根,不是你直觉里的「第 N 根」,调参时建议打印 array[0] 与 bar 对照验证。
::ArraySetAsSeries(buffer,as_series); class=class="str">"cmt">//--- Successful class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the data of the specified buffer as is | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CIndMSTF::GetData(const class="type">uint buffer_num,const class="type">int index) const { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class=class="str">"cmt">//--- If the indicator has buffers, class="kw">return "empty" value of the first one, otherwise EMPTY_VALUE class="kw">return(this.BuffersTotal()>class="num">0 ? this.BufferInitValue(class="num">0) : EMPTY_VALUE); } class=class="str">"cmt">//--- If an incorrect index is specified, class="kw">return the "empty" value of the specified buffer if(index<class="num">0 || index>(class="type">int)this.DataTotal(buffer_num)-class="num">1) class="kw">return this.BufferInitValue(buffer_num); class=class="str">"cmt">//--- Calculate the real index in the buffer array and class="kw">return the value at this index class="type">int n=class="type">int(this.DataTotal(buffer_num)-class="num">1-index); class="kw">return this.m_buffers[buffer_num].array[n]; } class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//| Returns data from specified buffer for specified symbol/timeframe | class=class="str">"cmt">//+-------------------------------------------------------------------+ class="type">class="kw">double CIndMSTF::GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">uint buffer_num,const class="type">int index) const { class=class="str">"cmt">//--- If current symbol/period of the chart is specified if(timeframe_to==::Period() && this.m_timeframe==::Period() && symbol_to==::Symbol() && this.m_symbol==::Symbol()) class="kw">return this.GetData(buffer_num,index); class=class="str">"cmt">//--- Find out which time of this class the current chart timeframe&class="macro">#x27;s bar, corresponding to the loop index, belongs to class="type">class="kw">datetime array[]; if(::CopyTime(symbol_to,timeframe_to,index,class="num">1,array)!=class="num">1) class="kw">return this.BufferInitValue(buffer_num); class=class="str">"cmt">//--- Using time of bar of current chart timeframe, find corresponding bar index of bar this class chart period class="type">int bar=iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]); class=class="str">"cmt">//--- If the bar is not found, class="kw">return the "empty" value set for the buffer if(bar==WRONG_VALUE) class="kw">return this.BufferInitValue(buffer_num); class=class="str">"cmt">//--- Return value from the indicator object buffer at the found index class="kw">return this.GetData(buffer_num,bar); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用三段缓冲判读指标线转折
多周期指标状态机里,判断一条线当前是上行、下行还是反转,靠的是连续三个偏移位的相对高低。下面这段方法一次性取 index、index+1、index+2 三个位置的值,用它们的关系给线打状态标签。 任何一处取到 EMPTY_VALUE 直接返回 LINE_STATE_NONE,避免空数据污染状态。随后用 NormalizeDouble 按品种小数位 m_digits 对齐,再比差值:value2>value1 且 value0>value1 视为向上反转(LINE_STATE_TURN_UP);value2<=value1 且 value0>value1 是明确上行(LINE_STATE_UP);value0==value1 则归为上行停顿(LINE_STATE_STOP_UP)。 下行侧镜像处理:value2<value1 且 value0<value1 是向下反转,value2>=value1 且 value0<value1 是下行,value0==value1 是下行停顿。其余杂乱关系一律 LINE_STATE_NONE。外汇与贵金属波动跳空频繁,EMPTY_VALUE 分支在实盘里触发概率不低,别默认三个值永远齐全。 同名重载方法允许传入 symbol_from 与 timeframes_from,空串或 NULL 时退回到当前图表的 Symbol(),这样同一套状态逻辑能跨品种跨周期复用,不用为每对 symbol/TF 重写判断。
ENUM_LINE_STATE CIndMSTF::BufferLineState(const class="type">uint buffer_num,const class="type">int index) const { const class="type">class="kw">double value0=this.GetData(buffer_num,index); const class="type">class="kw">double value1=this.GetData(buffer_num,index+class="num">1); const class="type">class="kw">double value2=this.GetData(buffer_num,index+class="num">2); if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) class="kw">return LINE_STATE_NONE; if(::NormalizeDouble(value2-value1,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0) class="kw">return LINE_STATE_TURN_UP; else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0) class="kw">return LINE_STATE_UP; else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0) class="kw">return LINE_STATE_STOP_UP; if(::NormalizeDouble(value2-value1,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0) class="kw">return LINE_STATE_TURN_DOWN; else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0) class="kw">return LINE_STATE_DOWN; else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0) class="kw">return LINE_STATE_STOP_DOWN; class="kw">return LINE_STATE_NONE; } ENUM_LINE_STATE CIndMSTF::BufferLineState(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">uint buffer_num,const class="type">int index) const { class="type">class="kw">string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
「跨周期取线状态时的柱索引映射」
做跨品种或跨周期指标调用时,最容易踩的坑是:源 K 线序号和当前图表的指标缓冲序号对不上。上面这段代码的核心逻辑,就是先判断「源是不是就是当前图」——如果是,直接把缓冲里的状态原样吐出来,省掉一次无谓的拷贝。 当源与目标不同步时,它用 CopyTime 按 index、index+1、index+2 三次各取 1 根柱的开盘时间,再拿 iBarShift 把这个时间映射回当前图表 m_symbol / m_timeframe 下的柱序号 bar0、bar1、bar2。三次拷贝都要求返回值严格等于 1,任何一次失败就打印具体错误码并返回 LINE_STATE_NONE,不会静默给错状态。 实盘里外汇和贵金属跳空常见,iBarShift 在缺失对应时间片时可能返回 WRONG_VALUE(-1),这段对每次映射失败都做了显式拦截。建议你在 MT5 里把这段塞进自定义指标,故意传一个错周期跑一下,看 PrintFormat 打出的 Error 是不是 4303(参数错误)或 4401(数组越界)这类常见码,验证容错是否到位。高风险品种下,状态误判可能直接带偏信号,映射层不能省。
ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from); class=class="str">"cmt">//--- If we get data from symbol/period equal to current chart, class="kw">return state from the buffer "as is" if(symbol==::Symbol() && symbol==this.m_symbol && timeframe==::Period() && timeframe==this.m_timeframe) class="kw">return this.BufferLineState(buffer_num,index); class=class="str">"cmt">//--- Declare variables to search for the required bars on the current chart class="type">class="kw">datetime array[class="num">1]; class="type">int bar0=WRONG_VALUE; class="type">int bar1=WRONG_VALUE; class="type">int bar2=WRONG_VALUE; class=class="str">"cmt">//--- Get the time of the first bar on the chart ::ResetLastError(); if(::CopyTime(symbol,timeframe,index,class="num">1,array)!=class="num">1) { ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),class="num">7),index,::GetLastError()); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Get index of the first bar in indicator object buffer based on bar opening time on the chart bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]); if(bar0==WRONG_VALUE) { ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),class="type">class="kw">string(array[class="num">0]),::GetLastError()); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Get the time of the second bar on the chart ::ResetLastError(); if(::CopyTime(symbol,timeframe,index+class="num">1,class="num">1,array)!=class="num">1) { ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),class="num">7),index+class="num">1,::GetLastError()); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Get index of the second bar in indicator object buffer based on bar opening time on the chart bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]); if(bar1==WRONG_VALUE) { ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),class="type">class="kw">string(array[class="num">0]),::GetLastError()); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Get the time of the third bar on the chart ::ResetLastError(); if(::CopyTime(symbol,timeframe,index+class="num">2,class="num">1,array)!=class="num">1) { ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),class="num">7),index+class="num">2,::GetLastError()); class="kw">return LINE_STATE_NONE; }
用三根K线位置判定指标线状态
这段逻辑核心是用连续三根bar在指标缓冲区的取值,推断当前线条处于反转、延续还是停滞。它先通过 iBarShift 把指标对象里第三根bar的开放时间映射到图表主时间轴,拿不到索引就直接返回未定义状态,避免后面算出脏数据。 取数阶段分别抓出 buffer_num 缓冲区内 bar0、bar1、bar2 三个偏移位的数值,只要其中一个是 EMPTY_VALUE 就整体退出。注意这里 bar2 是相对传入索引往前数第三根,bar0 是最新一根,顺序别在调用时搞反。 判定规则全部走 NormalizeDouble 按品种小数位对齐后再比大小:value2>value1 且 value0>value1 判为上拐(TURN_UP);value2<=value1 但 value0>value1 是向上运行中(UP);value0==value1 则归为向上停顿(STOP_UP)。下行对称处理,最后兜底返回 NONE。 实盘里外汇与贵金属波动跳空多,iBarShift 在跨周期取时间可能返回 WRONG_VALUE,日志会打出具体 symbol、周期和错误码,建议先在 EURUSD 的 M15 上手测一遍三种状态触发概率。
class=class="str">"cmt">//--- Get index of the third bar in indicator object buffer based on bar opening time on the chart bar2=::iBarShift(this.m_symbol,this.m_timeframe,array[class="num">0]); if(bar2==WRONG_VALUE) { ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),class="type">class="kw">string(array[class="num">0]),::GetLastError()); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1,class="num">2) relative to the passed index const class="type">class="kw">double value0=this.GetData(buffer_num,bar0); const class="type">class="kw">double value1=this.GetData(buffer_num,bar1); const class="type">class="kw">double value2=this.GetData(buffer_num,bar2); class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) class="kw">return LINE_STATE_NONE; class=class="str">"cmt">//--- Line upward reversal(value2>value1 && value0>value1) if(::NormalizeDouble(value2-value1,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0) class="kw">return LINE_STATE_TURN_UP; class=class="str">"cmt">//--- Line upward direction(value2<=value1 && value0>value1) else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)>class="num">0) class="kw">return LINE_STATE_UP; class=class="str">"cmt">//--- Line upward stop(value2<=value1 && value0==value1) else if(::NormalizeDouble(value2-value1,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0) class="kw">return LINE_STATE_STOP_UP; class=class="str">"cmt">//--- Line downward reversal(value2<value1 && value0<value1) if(::NormalizeDouble(value2-value1,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0) class="kw">return LINE_STATE_TURN_DOWN; class=class="str">"cmt">//--- Line downward direction(value2>=value1 && value0<value1) else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)<class="num">0) class="kw">return LINE_STATE_DOWN; class=class="str">"cmt">//--- Line downward stop(value2>=value1 && value0==value1) else if(::NormalizeDouble(value2-value1,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-value1,this.m_digits)==class="num">0) class="kw">return LINE_STATE_STOP_DOWN; class=class="str">"cmt">//--- Undefined state class="kw">return LINE_STATE_NONE; }
◍ 用两柱偏移判定指标线与参考位的相对状态
多周期状态框架里,判断一条指标线相对某参考位是上方、下方还是刚穿越,靠的是相邻两根柱的取值差。下面这个函数取 buffer_num 缓冲、index 处及其前一根(index+1)的值,对照 level0 / level1 双参考位给回枚举状态。 先取 value0=GetData(buffer_num,index) 与 value1=GetData(buffer_num,index+1);任一为 EMPTY_VALUE 直接回 LINE_STATE_NONE,避免脏数据参与比较。level 默认用 level0,仅当 level1 非 EMPTY_VALUE 时才改用 level1 作次参考。 所有大小判断都包了 NormalizeDouble(…, m_digits),用品种小数位抹掉浮点误差。比如 value1-level<0 且 value0-level0<0 判 BELOW;两根都 >0 判 ABOVE;value1<=level 而 value0>level0 是 CROSS_UP(上穿);反向则是 CROSS_DOWN。 还有 TOUCH_BELOW(前一根在下方、当前贴位)、EQUALS(两根都贴位)等细分态。注意原文中从上方触碰的分支误写了 LINE_STATE_TOUCH_BELOW,实应为 LINE_STATE_TOUCH_ABOVE,复制代码时建议手动修正,否则上触会被错归为下触。外汇与贵金属波动大,这类状态判定仅作概率参考,实盘须自担高风险。
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE) { class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1) relative to the passed index const class="type">class="kw">double value0=this.GetData(buffer_num,index); const class="type">class="kw">double value1=this.GetData(buffer_num,index+class="num">1); class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) class="kw">return LINE_STATE_NONE; class=class="str">"cmt">//--- Define the second level to compare class="type">class="kw">double level=(level1==EMPTY_VALUE ? level0 : level1); class=class="str">"cmt">//--- The line is below the level(value1<level && value0<level0) if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0) class="kw">return LINE_STATE_BELOW; class=class="str">"cmt">//--- The line is above the level(value1>level && value0>level0) if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0) class="kw">return LINE_STATE_ABOVE; class=class="str">"cmt">//--- The line crossed the level upwards(value1<=level && value0>level0) if(::NormalizeDouble(value1-level,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0) class="kw">return LINE_STATE_CROSS_UP; class=class="str">"cmt">//--- The line crossed the level downwards(value1>=level && value0<level0) if(::NormalizeDouble(value1-level,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0) class="kw">return LINE_STATE_CROSS_DOWN; class=class="str">"cmt">//--- The line touched the level from below(value1<level0 && value0==level0) if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0) class="kw">return LINE_STATE_TOUCH_BELOW; class=class="str">"cmt">//--- The line touched the level from above(value1>level0 && value0==level0) if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0) class="kw">return LINE_STATE_TOUCH_BELOW; class=class="str">"cmt">//--- Line is equal to the level value(value1==level0 && value0==level0) if(::NormalizeDouble(value1-level,this.m_digits)==class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0) class="kw">return LINE_STATE_EQUALS; class=class="str">"cmt">//--- Undefined state class="kw">return LINE_STATE_NONE; }
「指标线相对层级的六种状态判定」
在多周期指标封装里,判断某根指标线相对给定水平位是「下方 / 上方 / 上穿 / 下穿 / 触碰 / 等值」属于高频操作。下面这段方法接收品种、周期、缓冲区编号、偏移索引以及一或两个参考水平,返回枚举状态,省去策略层重复写比较逻辑。 方法先用 symbol_from 和 timeframes_from 的缺省值回退到当前图表上下文;随后取 index 与 index+1 两处缓冲区数值,任一为 EMPTY_VALUE 直接给 LINE_STATE_NONE,避免脏数据参与比较。 比较全部走 NormalizeDouble(value - level, m_digits) 做小数点对齐,差值符号决定状态:双负为 BELOW,双正为 ABOVE,前一根低于等于、当前高于则为 CROSS_UP,反向为 CROSS_DOWN;等于 0 且前一根异侧对应 TOUCH,两根皆等于水平则 EQUALS。 注意源码里「从上方触碰」分支误写了 LINE_STATE_TOUCH_BELOW,实际应返回 LINE_STATE_TOUCH_ABOVE,直接编译进 EA 会让上触判定和上穿后的贴线逻辑混淆,复制时务必改掉这一行。外汇与贵金属波动跳空频繁,EMPTY_VALUE 与点数位对齐处理不当会放大误判概率,建议先在 MT5 策略测试器用 EURUSD 15M 跑一遍状态分布再上实盘。
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const class="type">class="kw">string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE) { class=class="str">"cmt">//--- Determine the chart symbol/period passed to the method class="type">class="kw">string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from); ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from); class=class="str">"cmt">//--- Get the values of the indicator line with the shift(class="num">0,class="num">1) relative to the passed index const class="type">class="kw">double value0=this.GetDataTo(symbol,timeframe,buffer_num,index); const class="type">class="kw">double value1=this.GetDataTo(symbol,timeframe,buffer_num,index+class="num">1); class=class="str">"cmt">//--- If at least one of the values could not be obtained, class="kw">return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) class="kw">return LINE_STATE_NONE; class=class="str">"cmt">//--- Define the second level to compare class="type">class="kw">double level=(level1==EMPTY_VALUE ? level0 : level1); class=class="str">"cmt">//--- The line is below the level(value1<level && value0<level0) if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0) class="kw">return LINE_STATE_BELOW; class=class="str">"cmt">//--- The line is above the level(value1>level && value0>level0) if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0) class="kw">return LINE_STATE_ABOVE; class=class="str">"cmt">//--- The line crossed the level upwards(value1<=level && value0>level0) if(::NormalizeDouble(value1-level,this.m_digits)<=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)>class="num">0) class="kw">return LINE_STATE_CROSS_UP; class=class="str">"cmt">//--- The line crossed the level downwards(value1>=level && value0<level0) if(::NormalizeDouble(value1-level,this.m_digits)>=class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)<class="num">0) class="kw">return LINE_STATE_CROSS_DOWN; class=class="str">"cmt">//--- The line touched the level from below(value1<level0 && value0==level0) if(::NormalizeDouble(value1-level,this.m_digits)<class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0) class="kw">return LINE_STATE_TOUCH_BELOW; class=class="str">"cmt">//--- The line touched the level from above(value1>level0 && value0==level0) if(::NormalizeDouble(value1-level,this.m_digits)>class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0) class="kw">return LINE_STATE_TOUCH_BELOW; class=class="str">"cmt">//--- Line is equal to the level value(value1==level0 && value0==level0) if(::NormalizeDouble(value1-level,this.m_digits)==class="num">0 && ::NormalizeDouble(value0-level0,this.m_digits)==class="num">0) class="kw">return LINE_STATE_EQUALS; class=class="str">"cmt">//--- Undefined state class="kw">return LINE_STATE_NONE; }
类别与缓冲区描述方法的实现细节
在自定义多周期指标框架里,CategoryDescription 负责把枚举值转成可读文本。它从 ENUM_IND_CATEGORY 的字符串里截掉前 13 个字符,再强制首字母大写——比如原始串 'IND_CATEGORY_TREND' 处理后变成 'Trend'。 BufferDescription 先校验传入的 buffer_num 是否越界:上限是 BuffersTotal()-1。越界时按缓冲区总数拼出允许范围(单缓冲显示 '0',多缓冲显示 '0 - N-1')并写日志后返回空串。 若指标确实有缓冲,返回对应 m_buffers[buffer_num].descript;否则退回指标标题 m_title。SetBufferDescription 逻辑类似,但无缓冲或越界时直接 return,不抛空串,仅把描述写进指定缓冲结构。 下面这段是三个方法的原始实现,逐行对应上面逻辑: string CIndMSTF::CategoryDescription(void) 定义取类别描述的函数 string category=::StringSubstr(::EnumToString(this.m_category),13); 把枚举转字符串并去掉前13字符 if(category.Lower()) 先全转小写 category.SetChar(0,ushort(category.GetChar(0)-0x20)); 首字符减0x20回到大写ASCII return category; 返回处理好的类别名 string CIndMSTF::BufferDescription(const uint buffer_num) 取缓冲描述 if(buffer_num>this.BuffersTotal()-1) 越界判断 { string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1)); 拼合法范围 ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); 打印错误 return ""; 返回空 } return(this.BuffersTotal()>0 ? this.m_buffers[buffer_num].descript : this.m_title); 有缓冲取缓冲描述否则取标题 void CIndMSTF::SetBufferDescription(const uint buffer_num,const string descr) 设置缓冲描述 if(this.BuffersTotal()==0) 无缓冲直接退出 return; if(buffer_num>this.BuffersTotal()-1) 越界处理同BufferDescription { string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1)); ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); return; } this.m_buffers[buffer_num].descript=descr; 写入描述 void CIndMSTF::SetAsSeriesOff(void) 关闭数组时间序列索引 {
class="type">class="kw">string CIndMSTF::CategoryDescription(class="type">void) { class="type">class="kw">string category=::StringSubstr(::EnumToString(this.m_category),class="num">13); if(category.Lower()) category.SetChar(class="num">0,class="type">class="kw">ushort(category.GetChar(class="num">0)-0x20)); class="kw">return category; } class="type">class="kw">string CIndMSTF::BufferDescription(const class="type">uint buffer_num) { if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return ""; } class="kw">return(this.BuffersTotal()>class="num">0 ? this.m_buffers[buffer_num].descript : this.m_title); } class="type">void CIndMSTF::SetBufferDescription(const class="type">uint buffer_num,const class="type">class="kw">string descr) { if(this.BuffersTotal()==class="num">0) class="kw">return; if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return; } this.m_buffers[buffer_num].descript=descr; } class="type">void CIndMSTF::SetAsSeriesOff(class="type">void) {
◍ 缓冲区序列标志与数据量的读取接口
多时间框架指标类里,初始化阶段会统一把每个缓冲区的数组按时间序列属性关掉:遍历 0 到 BuffersTotal()-1,对 m_buffers[i].array 调用 ArraySetAsSeries(false)。这样后续按索引存取时,下标 0 对应的是最早一根 K 线,而不是 MT5 默认的最新棒。 IsSeries() 用来回读某个缓冲区是否仍被当作时间序列。它先校验 buffer_num 是否越界——超过 BuffersTotal()-1 就打印函数名、非法编号和可接受范围(单缓冲时显示“0”,多缓冲显示“0 - N-1”)并返回 false;合法则直接返回 ArrayGetAsSeries() 的布尔值。 DataTotal() 返回指定缓冲区数组的 Size()。同样先做越界检查,越界时返回 0 并输出日志;正常路径就是 this.m_buffers[buffer_num].array.Size(),也就是该缓冲实际装载了多少个元素。 开 MT5 自建指标时,若你手动改过 ArraySetAsSeries,记得用 IsSeries 确认状态,否则画出来的线可能左右颠倒。外汇与贵金属行情跳空频繁,这类底层数组错位会让信号整体偏移,属于高风险坑点。
class=class="str">"cmt">//--- In a loop through all indicator buffers, disable the array as timeseries flag for(class="type">int i=class="num">0;i<(class="type">int)this.BuffersTotal();i++) ::ArraySetAsSeries(this.m_buffers[i].array,false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns the timeseries flag of the given buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CIndMSTF::IsSeries(const class="type">uint buffer_num) const { class=class="str">"cmt">//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and class="kw">return &class="macro">#x27;false&class="macro">#x27; if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return false; } class=class="str">"cmt">//--- Return the timeseries flag of the array of the specified buffer class="kw">return (class="type">bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns the amount of data in the specified buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">uint CIndMSTF::DataTotal(const class="type">uint buffer_num) const { class=class="str">"cmt">//--- Validate the buffer number passed to method and, if number is incorrect, print a message to log and class="kw">return zero if(buffer_num>this.BuffersTotal()-class="num">1) { class="type">class="kw">string buff_limit=(this.BuffersTotal()==class="num">1 ? "class="num">0" : "class="num">0 - "+class="type">class="kw">string(this.BuffersTotal()-class="num">1)); ::PrintFormat("%s: Invalid buffer number passed(%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit); class="kw">return class="num">0; } class=class="str">"cmt">//--- Return the array size of the specified buffer class="kw">return this.m_buffers[buffer_num].array.Size(); }
「从基类派生指标类的构造套路」
多交易品种多周期指标体系的基类之下,最轻量的派生类只带一个构造函数。构造时把图表交易品种、周期和该类专有输入参数传进去,初始化列表里再转交给父类构造函数,就能在 MT5 里挂出对应指标对象。 以加速振荡指标(AC)为例,初始化行写 CIndMSTF(IND_AC,1,symbol,timeframe):第一个参数是指标枚举类型,第二个 1 代表缓冲区数量,后面跟 symbol 与 timeframe。类体内会拼一段参数描述字符串——若指标算在当前图表,字符串为空;若算在 EURUSD,H1 这类非当前环境,则写成 "(EURUSD,H1)" 形态。 小数位(Digits)不用在子类构造里设,它默认等于所算交易品种的小数位,由父类接管。真要改,要么在子类构造里覆盖,要么对象建完调 SetDigits()。累积/分布指标(A/D)就走了带参路径:把 ENUM_APPLIED_VOLUME 塞进 MqlParam 数组,顺手定了 Digits。 目前完整派生类清单已覆盖终端全部技术指标加自定义指标类,但实测只跑过基于主图单线移动平均的那几个。后续会做一套模板,把标准或自定义指标一键转成多品种多周期版本,并封装成集合类方便调用。外汇与贵金属市场波动剧烈、杠杆风险高,这类封装仅降低编码成本,不预示任何信号胜率。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Accelerator Oscillator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndAC : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("AC"); this.SetDescription("Accelerator Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Accumulation/Distribution indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndAD : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndAD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// used volume ) : CIndMSTF(IND_AD,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param);
指标类构造时的参数与描述落地
在 MT5 自建指标类的构造函数里,参数数组的首元素常被用来承载成交量类型。下面这段把 applied_volume 写进 m_param[0],类型标为 TYPE_UINT,若数组重设尺寸失败则直接打印错误码,方便你开 MT5 后对照终端 Experts 标签排查。 this.m_param[0].type=TYPE_UINT; this.m_param[0].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); 非当前图表品种或周期时,类内会用 StringFormat 拼出 "SYMBOL,PERIOD" 字样并包进括号,作为指标标题后缀;当前图则留空。A/D 指标在这里被归入 IND_CATEGORY_VOLUME 且 m_digits=0,而紧随其后的 CIndAC 构造函数走同样逻辑,只是指标名 "AC"、归类到 IND_CATEGORY_WILLIAMS,没有设小数位。 复制这两段构造逻辑到你的指标派生类,能在 MT5 导航树里直接看出非当前品种/周期的实例,避免多周期回测时把 EURUSD,H1 的 A/D 和主图搞混。外汇与贵金属波动剧烈,这类底层标识写错可能让信号源张冠李戴,实盘前务必在策略测试器里跑一遍符号校验。
this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("A/D"); this.SetDescription("Accumulation/Distribution"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_VOLUME; this.m_digits=class="num">0; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Accelerator Oscillator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndAC : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("AC"); this.SetDescription("Accelerator Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; class=class="str">"cmt">//--- Write description of line buffers
◍ A/D 与 ADX 指标类的封装骨架
在 MT5 自建指标封装层里,累积/派发(A/D)被写成 CIndAD 类,它继承自多周期框架基类 CIndMSTF,构造时只接收一个 applied_volume 参数,用来指定成交量来源(实时或历史成交)。 构造函数先调用 ArrayResize 把参数数组扩到长度 1,成功后才用 ZeroMemory 清零并写入 TYPE_UINT 类型的成交量枚举;若扩容返回不等于 1,则通过 PrintFormat 打出 ArrayResize failed 及 GetLastError 代码,方便在专家日志里定位内存异常。 非当前图表品种或周期时,类会把 symbol 与 timeframe 描述拼进参数后缀,例如 "EURUSD,H1",再合成 Name()+Parameters() 作为缓冲 0 的标题;m_digits 硬设为 0,因为 A/D 本身是累计值无量价小数。 紧接其后的 CIndADX 类同样继承 CIndMSTF,但构造签名里出现了 adx_period 整型 averaging period,说明 ADX 封装比 A/D 多一个周期维度,开 MT5 接这套基类时得先确认自己指标需要几个参数槽。
class CIndAD : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndAD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// used volume ) : CIndMSTF(IND_AD,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("A/D"); this.SetDescription("Accumulation/Distribution"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_VOLUME; this.m_digits=class="num">0; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class CIndADX : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndADX(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int adx_period class=class="str">"cmt">// averaging period
「ADX 与 ADXW 多周期封装的缓冲区约定」
在 MT5 里把 ADX 做成可跨品种跨周期调用的类时,构造函数先走 CIndMSTF(IND_ADX,3,symbol,timeframe),第二个参数 3 代表指标输出缓冲区数量:0 号是 MAIN_LINE(即 ADX 主线),1 号是 +DI,2 号是 -DI。 参数数组只扩到 1 个元素,type 设 TYPE_UINT,integer_value 写入 ADX 周期;若传入 adx_period 小于 1,则强制回退到 14——这是 Wilder 原始文献的默认平滑长度,也是终端里大多数预设的基准值。 非当前图表品种或周期时,构造函数会把 symbol 与 timeframe 拼进参数描述串,例如 "EURUSD,H1:14",用于区分不同实例。m_digits 固定为 2,表示 ADX 数值保留两位小数输出,和终端数据窗口显示精度一致。 ADXW 类(Wilder 版)构造头与 ADX 几乎同构,仅指标类型换成 IND_ADXW,缓冲区编号含义不变。实盘调用时若看到 ArrayResize failed 的 PrintFormat 报错,优先查 m_param 内存分配,外汇与贵金属波动大,这类底层错误可能让指标静默失效,需人工复核。
) : CIndMSTF(IND_ADX,class="num">3,symbol,timeframe) { class=class="str">"cmt">// Номера буферов: class="num">0 - MAIN_LINE, class="num">1 - PLUSDI_LINE, class="num">2 - MINUSDI_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(adx_period<class="num">1 ? class="num">14 : adx_period); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),adx_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("ADX"); this.SetDescription("Average Directional Movement Index"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=class="num">2; class=class="str">"cmt">//--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers this.SetBufferDescription(MAIN_LINE,this.m_title); this.SetBufferDescription(PLUSDI_LINE,"+DI"); this.SetBufferDescription(MINUSDI_LINE,"-DI"); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Average Directional Movement Index Wilder indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndADXW : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndADXW(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int adx_period class=class="str">"cmt">// averaging period ) : CIndMSTF(IND_ADXW,class="num">3,symbol,timeframe) { class=class="str">"cmt">// Номера буферов: class="num">0 - MAIN_LINE, class="num">1 - PLUSDI_LINE, class="num">2 - MINUSDI_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError();
ADX 与鳄口指标的封装细节
在自定义指标类的初始化里,先给参数数组留一个槽位:若 ArrayResize 返回 1 说明成功,随即 ZeroMemory 清空,并把第 0 个参数类型标为 TYPE_UINT,整数值取传入的 adx_period,小于 1 时自动回退到 14——这是 Wilder ADX 最常见的默认周期。 若扩容失败,直接用 PrintFormat 把函数名和 GetLastError 代码打出来,方便在 MT5 Experts 日志里定位内存或句柄问题。 参数描述字符串会判断是否用当前图表的品种与周期:非当前图时拼成 "SYMBOL,PERIOD:周期数" 的样子,当前图则留空,最终写成 (EURUSD,H1:14) 这种可读标识。 随后 SetName 写死 "ADX Wilder",类目归 IND_CATEGORY_TREND,小数位锁 2 位;三条线缓冲分别标为标题本体、+DI、-DI,鳄口类 CIndAlligator 的构造则继续接收 jaw/teeth 的周期与水平偏移参数,沿同一套 MSTF 基类思路挂线。 外汇与贵金属行情跳空频繁,这类封装若周期参数乱传,ADX 读数可能失真,开 MT5 把 adx_period 改成 7 或 30 对比副图斜率,能直观验证默认 14 的平滑倾向。
if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(adx_period<class="num">1 ? class="num">14 : adx_period); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),adx_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("ADX Wilder"); this.SetDescription("Average Directional Movement Index Wilder"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=class="num">2; class=class="str">"cmt">//--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers this.SetBufferDescription(MAIN_LINE,this.m_title); this.SetBufferDescription(PLUSDI_LINE,"+DI"); this.SetBufferDescription(MINUSDI_LINE,"-DI"); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Alligator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndAlligator : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndAlligator(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int jaw_period, class=class="str">"cmt">// period for calculating jaws const class="type">int jaw_shift, class=class="str">"cmt">// horizontal shift of jaws const class="type">int teeth_period, class=class="str">"cmt">// period for calculating teeth
◍ 封装鳄鱼指标的参数守卫
在 MT5 自定义指标类里,把鳄鱼(Alligator)的三条线塞进一个参数数组时,最容易被忽略的是周期下限。下面这段构造函数把 jaw/teeth/lips 的周期分别做了 <1 则回退默认值的处理:颚线回退 13、齿线回退 8、唇线回退 5,对应经典鳄鱼设置的原始参数。 数组 m_param 被 ResetLastError 后尝试 ArrayResize 到 8,只有返回值为 8 才继续填充,否则后续缓冲区全是野值。索引 0/2/4 放三条线的周期(TYPE_UINT),1/3/5 放水平位移(TYPE_INT),6 放平滑方式,7 留作价格类型或句柄。 缓冲序号固定为 0= jaw、1= teeth、2= lips,调用 CIndMSTF(IND_ALLIGATOR,3,...) 时第二个参数 3 就是告诉父类要开三条线。你在自己重写的指标里若改了默认周期,记得同步改这里的三元表达式,不然回测时可能倾向出现和原生鳄鱼不一致的相位偏移。外汇与贵金属品种波动跳空频繁,这类多线指标在高杠杆下误判风险偏高,参数改动后务必先在策略测试器跑一轮。
const class="type">int teeth_shift, class=class="str">"cmt">// horizontal shift of teeth const class="type">int lips_period, class=class="str">"cmt">// period for calculating lips const class="type">int lips_shift, class=class="str">"cmt">// horizontal shift of lips const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_ALLIGATOR,class="num">3,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - GATORJAW_LINE, class="num">1 - GATORTEETH_LINE, class="num">2 - GATORLIPS_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">8)==class="num">8) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- period for jaw line calculation this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(jaw_period<class="num">1 ? class="num">13 : jaw_period); class=class="str">"cmt">//--- horizontal shift of the jaw line this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=jaw_shift; class=class="str">"cmt">//--- period for teeth line calculation this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(teeth_period<class="num">1 ? class="num">8 : teeth_period); class=class="str">"cmt">//--- horizontal shift of teeth line this.m_param[class="num">3].type=TYPE_INT; this.m_param[class="num">3].integer_value=teeth_shift; class=class="str">"cmt">//--- period for lip line calculation this.m_param[class="num">4].type=TYPE_UINT; this.m_param[class="num">4].integer_value=(lips_period<class="num">1 ? class="num">5 : lips_period); class=class="str">"cmt">//--- horizontal shift of lips line this.m_param[class="num">5].type=TYPE_INT; this.m_param[class="num">5].integer_value=lips_shift; class=class="str">"cmt">//--- smoothing type this.m_param[class="num">6].type=TYPE_UINT; this.m_param[class="num">6].integer_value=ma_method; class=class="str">"cmt">//--- price type or handle
「给鳄鱼指标灌入参数与缓冲描述」
在 CIndAlligator 的初始化尾部,代码把 applied_price 写进第 8 个参数槽(索引 7),类型标为 TYPE_UINT。若前面 ArrayResize 失败,则直接 PrintFormat 打出函数名与 GetLastError 的错误码,方便在 MT5 Experts 日志里定位内存分配问题。 随后用 current 变量判断当前加载的是不是图表同品种同周期:若是,symbol_period 为空串;若否,则拼成 "EURUSD,H1" 这类字样。参数描述按 "([symbol,period:]jaw,teeth,lips)" 格式生成,例如非当前图可能为 "(EURUSD,H1:13,8,5)",这串会进 SetParameters 并最终显示在指标子窗口标题上。 三路缓冲分别用 SetBufferDescription 写 Jaws/Teeth/Lips 的标签,并用 SetBufferShift 写入 jaw_shift、teeth_shift、lips_shift 的偏移量。MT5 里若你改了默认偏移(鳄鱼原始为 8/5/3),这里就是唯一生效入口,开 MT5 导航到自定义指标源码核对偏移能避免画图错位。
this.m_param[class="num">7].type=TYPE_UINT; this.m_param[class="num">7].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("Alligator"); this.SetDescription("Alligator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; class=class="str">"cmt">//--- Write descriptions of GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE line buffers this.SetBufferDescription(GATORJAW_LINE,::StringFormat("Jaws(%s%lu)", (current ? "" : symbol_period+":"),jaw_period)); this.SetBufferDescription(GATORTEETH_LINE,::StringFormat("Teeth(%s%lu)",(current ? "" : symbol_period+":"),teeth_period)); this.SetBufferDescription(GATORLIPS_LINE,::StringFormat("Lips(%s%lu)", (current ? "" : symbol_period+":"),lips_period)); class=class="str">"cmt">//--- Write offsets to buffers GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE this.SetBufferShift(GATORJAW_LINE,jaw_shift); this.SetBufferShift(GATORTEETH_LINE,teeth_shift); this.SetBufferShift(GATORLIPS_LINE,lips_shift); } };
AMA 多周期封装里的参数兜底逻辑
在 MT5 里把 AMA(自适应均线)做成跨周期类时,构造函数先把 5 个参数塞进 m_param 数组:AMA 周期、快线周期、慢线周期、水平偏移、应用价格。数组大小固定为 5,若 ArrayResize 返回非 5 就直接打错误日志,避免后续越界。 参数写入时有硬兜底:ama_period 小于 1 时强制取 9,fast_ma_period 小于 1 取 2,slow_ma_period 小于 1 取 30。这三个默认值意味着你在 EA 里漏传周期参数,指标也不会崩,而是回退到一套偏保守的快慢组合(9/2/30)。 描述字符串会判断是否当前图表:非当前品种或周期时,把 symbol 和 timeframe 拼到参数说明前。这样在「小布盯盘」里同时挂 EURUSD 与 XAUUSD 的 AMA 实例,日志里能直接区分来源。外汇与贵金属杠杆高,跨周期信号仅作概率参考,实盘前请在策略测试器跑至少 3 个月 tick 数据。
const class="type">int slow_ma_period, class=class="str">"cmt">// slow MA period const class="type">int ama_shift, class=class="str">"cmt">// horizontal shift of the indicator const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_AMA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">5)==class="num">5) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- AMA period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ama_period<class="num">1 ? class="num">9 : ama_period); class=class="str">"cmt">//--- fast MA period this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(fast_ma_period<class="num">1 ? class="num">2 : fast_ma_period); class=class="str">"cmt">//--- slow MA period this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(slow_ma_period<class="num">1 ? class="num">30 : slow_ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">3].type=TYPE_INT; this.m_param[class="num">3].integer_value=ama_shift; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">4].type=TYPE_UINT; this.m_param[class="num">4].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),ama_period,fast_ma_period,slow_ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("AMA");
◍ 在 CIndMSTF 派生类里固化指标身份
把 AMA、AO、ATR 写成继承自 CIndMSTF 的子类,核心不是在构造函数里算值,而是把「它是谁、归哪类、小数点几位」一次性钉死。AMA 构造函数里 m_category 被设为 IND_CATEGORY_TREND,AO 则是 IND_CATEGORY_WILLIAMS,分类直接影响小布知识库里指标树的归位逻辑。 m_digits 统一取 Digits()+1,也就是在品种报价精度上多留一位,避免 AMA/AO/ATR 这类派生量在 H1 黄金(报价精度 2 位)上显示成 0.0 而丢细节。SetBufferShift(0,ama_shift) 把 AMA 主缓冲按 ama_shift 整体平移,用来做信号延迟对比时很实用。 AO 构造函数里有一段 current 判断:当 symbol 和 timeframe 与当前图表一致时,param 留空;否则用 StringFormat("%s,%s") 把「品种,周期」拼进标题。这样你挂 EURUSD 的 M15 AO 到 H1 图表,缓冲描述会自动变成「AO(EURUSD,M15)」,回测时一眼能分清来源。 开 MT5 把这三段类定义塞进你的指标头文件,编译后拖到图表看 Data Window——缓冲名若没带周期后缀,八成是 current 分支没走对,重点查 Symbol() 与 ::Symbol() 是否同实例。
this.SetDescription("Adaptive Moving Average"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ama_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Awesome Oscillator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndAO : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndAO(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AO,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("AO"); this.SetDescription("Awesome Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Average True Range indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndATR : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndATR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period class=class="str">"cmt">// averaging period ) : CIndMSTF(IND_ATR,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError();
「给 ATR 与 Bears Power 塞进参数和描述」
在 MT5 自定义指标封装里,构造函数要先给参数数组留位置。上面这段对 ATR 的处理很直接:ArrayResize 把 m_param 扩到 1 个元素,成功才继续;失败就 PrintFormat 把错误码打出来,方便你开 MT5 终端看日志排查。 ATR 的周期参数有个兜底逻辑——传入 ma_period 小于 1 时自动填 14,否则用传入值。Bears Power 这边几乎是同一套骨架,只是兜底周期写的是 13,不是 14,复制代码时别顺手改错。 描述信息也得顺手挂上:current 判断是否在当前图表品种和周期,不是的话就把 symbol,period 拼进参数串。最后 SetName 写 "ATR"、分类归到 IND_CATEGORY_OSCILLATOR,缓冲区 0 的描述用 Name()+Parameters() 拼出来,指标在导航树里就不会显示成无名氏。
if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("ATR"); this.SetDescription("Average True Range"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Bears Power indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndBears : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndBears(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period class=class="str">"cmt">// averaging period ) : CIndMSTF(IND_BEARS,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">13 : ma_period); } else
牛力指标类的参数初始化写法
在自定义多周期指标框架里,Bulls Power 指标类 CIndBulls 的构造函数承接了均线周期入参,并把它塞进参数数组。若调用方传入的 ma_period 小于 1,代码会强制回退到 13——这是 MT5 标准 Bulls Power 的默认均线周期,直接写死能避免后续计算出现除零或缓冲区异常。 数组扩容这一步用 ArrayResize 把 m_param 拉到长度 1,失败就通过 PrintFormat 把 __FUNCTION__ 和 GetLastError 抛到日志,方便在 MT5 Experts 标签页里定位。成功后才 ZeroMemory 清空结构体,再把 m_param[0] 标成 TYPE_UINT 并写入周期值。 判定当前图表还是外部品种周期,靠的是 this.Symbol()==::Symbol() 且 this.Timeframe()==::Period() 的布尔组合。非当前图表的品种和周期会拼进 param 字符串,例如 "XAUUSD,H1:13",当前图则只留 "(13)"。这种写法让你复制类到自己的EA时,能在指标副标题一眼看清它挂在哪个品种周期。 外汇与贵金属杠杆品种波动剧烈,这类指标仅描述多空力度偏差,实际信号需结合价格结构过滤,复盘时请先在策略测试器跑历史数据验证。
class CIndBulls : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndBulls(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period class=class="str">"cmt">// averaging period ) : CIndMSTF(IND_BULLS,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">13 : ma_period); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
◍ 布林带类的参数装配细节
在封装布林带指标时,CIndBands 构造函数先通过 CIndMSTF(IND_BANDS,3,symbol,timeframe) 声明这是一个含 3 条缓冲区的振荡类指标:0 号为基础线 BASE_LINE,1 号为上轨 UPPER_BAND,2 号为下轨 LOWER_BAND。 参数数组用 ArrayResize 扩到 4 个元素后才写入,顺序固定为周期、偏移、标准差倍数、应用价格。其中周期若传入小于 1,代码会强制回退到 20——这是 MT5 内置指标的默认中轨周期,直接在类里兜底,避免调用方漏填导致异常。 标准差倍数 deviation 与应用价格 applied_price 紧随其后填入 m_param 数组。实盘里若你想把通道收紧到 1.5 个标准差做黄金 5 分钟突破,就把 deviation 设为 1.5,并在上轨/下轨缓冲区读取后做斜率判断,外汇与贵金属波动剧烈,参数过窄可能频繁假突破。
class CIndBands : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndBands(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int bands_period, class=class="str">"cmt">// central line calculation period const class="type">int bands_shift, class=class="str">"cmt">// horizontal shift of the indicator const class="type">class="kw">double deviation, class=class="str">"cmt">// number of standard deviations const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_BANDS,class="num">3,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - BASE_LINE, class="num">1 - UPPER_BAND, class="num">2 - LOWER_BAND class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- central line calculation period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(bands_period<class="num">1 ? class="num">20 : bands_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=bands_shift; class=class="str">"cmt">//--- number of standard deviations
「布林带与CCI封装里的参数落点」
这段封装把布林带指标的核心参数直接写进 m_param 数组:索引 2 存偏差值 deviation,类型标为 TYPE_DOUBLE;索引 3 存应用价格 applied_price,类型标为 TYPE_UINT。手动改这两个索引的赋值,就能在实例化时绕开默认输入组,直接钉死偏差和价格类型。 判定当前图表还是外部图表靠一句 current 比较:Symbol() 与 Period() 都一致才为真。若非当前图,参数描述里会拼上 "符号,周期" 前缀,例如 "XAUUSD,H1",否则留空。这决定了 MT5 数据窗口里指标标题长什么样。 缓冲区描述与偏移是最后一步:BASE_LINE、UPPER_BAND、LOWER_BAND 三条线都加了 bands_shift 的水平偏移,m_digits 取 Digits()+1,意味着布林带输出比品种报价多一位小数。外汇与贵金属杠杆高、滑点随机,这类封装参数若设错,回测与实盘可能偏差明显,需开 MT5 用样例符号核对。 下面这段 CCI 类构造仅声明了继承与入参:ma_period 为平均周期,applied_price 为价格类型或句柄,父类 CIndMSTF 以 IND_CCI 和缓冲区数 1 初始化。要验证可复制该签名建个 EA,打印 m_title 看是否如期带周期后缀。
this.m_param[class="num">2].type=TYPE_DOUBLE; this.m_param[class="num">2].double_value=deviation; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),bands_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Bands"); this.SetDescription("Bollinger Bands"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Description of line buffers BASE_LINE, UPPER_BAND and LOWER_BAND this.SetBufferDescription(BASE_LINE,this.m_title+" Middle"); this.SetBufferDescription(UPPER_BAND,this.m_title+" Upper"); this.SetBufferDescription(LOWER_BAND,this.m_title+" Lower"); class=class="str">"cmt">//--- Write offsets to the BASE_LINE, UPPER_BAND and LOWER_BAND buffers this.SetBufferShift(BASE_LINE,bands_shift); this.SetBufferShift(UPPER_BAND,bands_shift); this.SetBufferShift(LOWER_BAND,bands_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Commodity Channel Index indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndCCI : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndCCI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_CCI,class="num">1,symbol,timeframe)
给 CCI 包装类塞参数与描述
在自定义指标封装里,CCI 的构造段先把参数数组定死为 2 个元素:索引 0 放均线周期,索引 1 放价格类型或句柄。若外部传入的 ma_period 小于 1,内部兜底成 14;小于 2 则取 2,否则用原值——这意味着你直接传 0 也能跑,但周期会被 silently 拉到 14。 下面这段代码就是这段逻辑的原貌,逐行拆完你能直接抄进自己的指标壳子: ArrayResize 返回 2 才继续,否则用 PrintFormat 把错误码打出来,方便在 MT5 Experts 日志里抓 ArrayResize failed。随后 current 变量比对图表符号与周期,非当前图就把 "符号,周期" 拼进参数描述,让多周期实例在数据窗口里不撞名。 最后 SetName 写死 "CCI"、类别挂到 IND_CATEGORY_OSCILLATOR、小数位锁 2,缓冲区 0 的描素用 Name()+Parameters() 自动生成。外汇与贵金属行情跳空多,这类封装若跨周期调用,需留意非当前图符号的点差与报价延迟可能让振荡器信号滞后。
{
class=class="str">"cmt">//--- Set the size of the parameter array and fill it
::ResetLastError();
if(::ArrayResize(this.m_param,class="num">2)==class="num">2)
{
::ZeroMemory(this.m_param);
class=class="str">"cmt">//--- averaging period
this.m_param[class="num">0].type=TYPE_UINT;
this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period<class="num">2 ? class="num">2 : ma_period);
class=class="str">"cmt">//--- price type or handle
this.m_param[class="num">1].type=TYPE_UINT;
this.m_param[class="num">1].integer_value=applied_price;
}
else
::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
class=class="str">"cmt">//--- Create description of parameters
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
this.SetParameters(param);
this.SetName("CCI");
this.SetDescription("Commodity Channel Index");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_OSCILLATOR;
this.m_digits=class="num">2;
class=class="str">"cmt">//--- Write description of line buffers
this.SetBufferDescription(class="num">0,this.m_title);
}◍ 蔡金振荡器构造里的参数兜底逻辑
在 MT5 自建指标类里封装 Chaikin Oscillator,构造函数先把四个参数塞进 m_param 数组:快线周期、慢线周期、平滑方式、成交量类型。数组长度写死为 4,若 ArrayResize 返回的不是 4,就直接 PrintFormat 把错误码甩出来,方便你开盘前在专家日志里抓初始化失败。 快线周期如果传进来小于 1,代码会强制落到 3;慢线周期小于 1 则落到 10。这两个默认值意味着你漏传参数时,指标不会崩,但会以 3/10 这种偏敏感的组合跑出来,黄金 15 分钟图上容易出毛刺信号。 非当前图表品种或周期时,参数描述会拼上 symbol_period 前缀,最终指标名显示为 CHO 加括号参数。m_digits 设成 0 说明该振荡器不按价格小数位显示,而是整数刻度,写面板时要留好列宽。外汇与贵金属杠杆高,这类派生指标仅作量能背离参考,信号失效概率不低,实盘前务必在策略测试器跑一遍。
const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// used volume ) : CIndMSTF(IND_CHAIKIN,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- fast period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(fast_ma_period<class="num">1 ? class="num">3 : fast_ma_period); class=class="str">"cmt">//--- slow period this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(slow_ma_period<class="num">1 ? class="num">10 : slow_ma_period); class=class="str">"cmt">//--- smoothing type this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=ma_method; class=class="str">"cmt">//--- used volume this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),slow_ma_period,fast_ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("CHO"); this.SetDescription("Chaikin Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=class="num">0; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); }
「封装 DEMA 指标类的构造逻辑」
在 MT5 里用面向对象方式调双指数移动平均(DEMA),核心是把周期、偏移、价格类型塞进参数数组。下面这段类构造直接继承自多周期趋势基类,把 IND_DEMA 的句柄初始化收敛成一个可复用对象。 构造函数先调用 ArrayResize 把 m_param 扩到 3 个槽位,失败就打印错误码;成功则用 ZeroMemory 清场,再分别写入周期(TYPE_UINT,小于 1 时回退到 14)、偏移(TYPE_INT)、应用价格(TYPE_UINT)。注意 ma_period 默认值兜底为 14,这是 DEMA 常见的初参,实盘调参时建议从 14 起步观察平滑度。 非当前图表品种或周期时,类会自动把 symbol、timeframe 拼进参数描述串,方便在跨周期脚本里追溯实例来源。最后 SetName 写死 "DEMA"、分类挂到 IND_CATEGORY_TREND,说明它本质仍归趋势类。外汇与贵金属波动剧烈,DEMA 滞后性虽低于 EMA,但拐点假信号概率仍不低,上线前务必在策略测试器跑历史段验证。
class=class="str">"cmt">//| Double Exponential Moving Average indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndDEMA : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndDEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const class="type">int ma_shift, class=class="str">"cmt">// horizontal indicator shift const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_DEMA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">3)==class="num">3) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=ma_shift; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("DEMA"); this.SetDescription("Double Exponential Moving Average"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND;
DeMarker 指标类的封装与参数兜底
在 MT5 自建指标库里,把 DeMarker 封装成 CIndDeM 类可以省掉重复挂参数的麻烦。构造函数接收 symbol、timeframe 和 ma_period,底层调 CIndMSTF 初始化,缓冲区数量固定为 1。 参数数组只装一个 UINT 类型的周期值,若传入 ma_period 小于 1,代码直接兜底成 14——这是 DeMarker 在 MT5 里的经典默认周期,多数外汇与贵金属品种回看 14 根 K 线时震荡区识别较稳。 非当前图表品种或周期时,标题会自动拼上 "SYMBOL,TIMEFRAME:period" 字样,避免多周期实例混淆;m_digits 写死为 3,意味着 DeM 输出值保留三位小数,和原生指标面板一致。 缓冲区描述用 m_title 填充,分类归到 IND_CATEGORY_OSCILLATOR。开 MT5 把这段抄进 include 头文件,就能用 CIndDeM 一键生成跨周期 DeMarker 句柄,外汇贵金属波动剧烈,实盘前先用策略测试器验证信号延迟。
class CIndDeM : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndDeM(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period class=class="str">"cmt">// averaging period ) : CIndMSTF(IND_DEMARKER,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("DeM"); this.SetDescription("DeMarker"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=class="num">3; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } };
◍ 封装包络线指标的类构造逻辑
在 MT5 里用面向对象方式调 Envelopes,常见做法是继承一个多周期基础类(这里叫 CIndMSTF),把指标类型和缓冲区数量在初始化列表里写死:IND_ENVELOPES 配 2 个缓冲,分别对应上轨和下轨。 构造函数收 6 个参数:交易品种、周期、均线周期、水平偏移、平滑方式、应用价格、以及偏离度。代码里把参数塞进 m_param 数组,长度固定为 5;若 ArrayResize 返回不是 5,就打印错误号并跳过填充,这种防御写法能避免空头指针把 EA 跑崩。 注意 ma_period 若小于 1 会被强行纠正为 14——这是包络线中轨默认均线周期。偏离度 deviation 存在 m_param[4].double_value,而非整型字段,说明它是百分比形式的浮点参数(如 0.1 代表 ±10%)。 开 MT5 自建脚本时,直接抄这段构造体就能快速挂一个双轨包络线;外汇与贵金属杠杆高、波动剧烈,参数误设可能导致信号频繁假突破,实盘前务必在策略测试器跑历史数据验证。
class=class="str">"cmt">//| Envelopes indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndEnvelopes : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndEnvelopes(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// middle line calculation period const class="type">int ma_shift, class=class="str">"cmt">// horizontal shift of the indicator const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_APPLIED_PRICE applied_price, class=class="str">"cmt">// price type or handle const class="type">class="kw">double deviation class=class="str">"cmt">// deviation of envelope borders from the middle line ) : CIndMSTF(IND_ENVELOPES,class="num">2,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - UPPER_LINE, class="num">1 - LOWER_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">5)==class="num">5) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- central line calculation period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=ma_shift; class=class="str">"cmt">//--- smoothing type this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=ma_method; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_price; class=class="str">"cmt">//--- deviation of envelope borders from the muddle line this.m_param[class="num">4].type=TYPE_UINT; this.m_param[class="num">4].double_value=deviation; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters
「Envelopes 与 Force Index 的初始化差异」
在封装指标类时,Envelopes 会在非当前图表品种或周期时,把符号与周期拼进参数串。代码里用 current 判断是否为当前图,若不是则通过 StringFormat("%s,%s") 把 Symbol() 和 TimeframeDescription() 合并,最终参数形如 (EURUSD,H1:13),当前图则只留周期数字。 Envelopes 还显式设定了上下轨缓冲描述:UPPER_LINE 追加 " Upper"、LOWER_LINE 追加 " Lower",并把缓冲 0 的位移设成 ma_shift。m_digits 取 Digits()+1,意味着显示精度比报价多一位,回测时若发现小数点位数异常可先查这里。 Force Index 类构造走的是另一套:ArrayResize(m_param,3) 成功才继续,平均周期小于 1 时自动回退到 13。这个 13 是硬编码兜底值,实盘接 AIGC 信号前建议在 MT5 里改参验证,外汇与贵金属杠杆高,参数错配可能放大回撤概率。
class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Envelopes"); this.SetDescription("Envelopes"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Description of UPPER_LINE and LOWER_LINE line buffers this.SetBufferDescription(UPPER_LINE,this.m_title+" Upper"); this.SetBufferDescription(LOWER_LINE,this.m_title+" Lower"); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ma_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Force Index indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndForce : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndForce(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// volume type for calculation ) : CIndMSTF(IND_FORCE,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">3)==class="num">3) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">13 : ma_period); class=class="str">"cmt">//--- smoothing type this.m_param[class="num">1].type=TYPE_UINT;
在类里给指标挂上身份与缓冲说明
上面这段截取自两个指标类的构造函数,核心动作是把运行参数和显示属性写进对象内部。Force Index 那段在数组扩容成功后,把均线周期、均价算法、成交量类型分别塞进 m_param[0~2],失败则用 PrintFormat 把错误码甩出来,方便在 MT5 Experts 日志里定位 ArrayResize 为什么没跑通。 紧接着它判断当前图表的品种周期是否和实例一致:不一致就把 "Symbol,Period" 拼进 param 字符串,一致就留空。最终通过 SetParameters、SetName、SetDescription 把 "Force"、"Force Index" 及标题写进去,并把 m_digits 设成 Digits()+1,意味着 Force 线的小数位比报价多一位,EURUSD 这类五位平台就会显示 5+1=6 位。 Fractals 类更轻量,只区分当前/非当前图表,缓冲 0 和 1 分别对应上、下分形线,构造函数里直接 SetName("Fractals") 收尾。你在自己写指标封装时,照这个套路把 symbol_period 拼进参数描述,子图标题就能自动带上品种周期,不用手动改标签。 开 MT5 新建个脚本把 CIndFractals 实例化在非当前周期(比如 XAUUSD,H1 挂在 M5 图),调出 m_title 打印,能直接看到 "Fractals(XAUUSD,H1)" 这类自动生成串,验证拼接逻辑是否如预期。外汇与贵金属杠杆高,这类底层封装错误若没捕获,可能让 EA 静默失效,复盘时务必核对 GetLastError 返回值。
this.m_param[class="num">1].integer_value=ma_method; class=class="str">"cmt">//--- volume type for calculation this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Force"); this.SetDescription("Force Index"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fractals indicator class class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndFractals : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndFractals(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,class="num">2,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - UPPER_LINE, class="num">1 - LOWER_LINE class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("Fractals");
◍ 分形自适应均线的参数装配
在 MT5 自定义指标框架里,CIndFrAMA 类承接了分形自适应移动平均(FRAMA)的实例化工作。构造函数接收 symbol、timeframe、ma_period、ma_shift 与 applied_price 五个入参,并在初始化列表里把指标类型锁死为 IND_FRAMA、缓冲区数量设为 1。 参数落库前先调 ArrayResize 把 m_param 扩到 3 个槽位,返回值为 3 才继续;否则直接 PrintFormat 打出 ArrayResize failed 与 GetLastError 代码,方便在专家日志里定位内存申请失败。 三个槽位分别写死类型与取值:0 号存 TYPE_UINT 的平均周期,若传入 ma_period 小于 1 则兜底成 14;1 号存 TYPE_INT 的水平偏移 ma_shift;2 号存 TYPE_UINT 的 applied_price 枚举或句柄。 current 布尔量通过比较本对象 Symbol/Timeframe 与全局 ::Symbol()、::Period() 是否一致,决定参数描述里要不要追加非当前图表的品种与周期后缀。开 MT5 把 ma_period 故意传 0,能在日志看到周期自动回退到 14 的现场。
class CIndFrAMA : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndFrAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const class="type">int ma_shift, class=class="str">"cmt">// horizontal shift of the indicator const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_FRAMA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">3)==class="num">3) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=ma_shift; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
「用类封装给 FRAMA 和 Gator 打上身份标签」
在 MT5 自定义指标开发里,给指标实例设置可读的元信息,是后续在「小布盯盘」里做多指标联动的前提。下面这段构造逻辑,先拼出带品种、周期、均线周期的参数串,再统一写入名称与分类。
string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
this.SetParameters(param);
this.SetName("FRAMA");
this.SetDescription("Fractal Adaptive Moving Average");
this.m_title=this.Name()+this.Parameters();
this.m_category=IND_CATEGORY_TREND;
this.m_digits=::Digits()+1;
this.SetBufferDescription(0,this.m_title);
this.SetBufferShift(0,ma_shift);
逐行看:前两句用 StringFormat 拼出形如 (EURUSD,H1:14) 的 param,current 为真时省略品种周期;SetParameters 把它挂到指标参数里,SetName 写死 "FRAMA",SetDescription 补全称;m_title 是名称加参数,用于缓冲区 0 的图例。m_category 设为趋势类,m_digits 在账户 Digits() 基础上 +1,避免 fractal 自适应均线的小数位不够。
紧接着的 CIndGator 类继承 CIndMSTF,构造函数接收 jaw/teeth/lips 三组周期与位移,外加平滑方式与应用价。缓冲区固定 4 个:0 和 2 是上下直方图,1 和 3 是对应的颜色缓冲。开 MT5 把这段类骨架塞进你的 include 文件,改 ma_period 从 14 调到 21,刷新副图就能看到标题串跟着变,验证参数注入是否生效。外汇与贵金属波动剧烈,这类封装只解决识别问题,信号本身不代表方向确定性。
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("FRAMA"); this.SetDescription("Fractal Adaptive Moving Average"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ma_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Gator Oscillator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndGator : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndGator(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int jaw_period, class=class="str">"cmt">// period for jaw line calculation const class="type">int jaw_shift, class=class="str">"cmt">// horizontal shift of jaw line const class="type">int teeth_period, class=class="str">"cmt">// period for calculating teeth line const class="type">int teeth_shift, class=class="str">"cmt">// horizontal shift of teeth line const class="type">int lips_period, class=class="str">"cmt">// period for calculating lip line const class="type">int lips_shift, class=class="str">"cmt">// horizontal shift of lip line const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_GATOR,class="num">4,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - UPPER_HISTOGRAM, class="num">1 - class="type">class="kw">color buffer of the upper histogram, class="num">2 - LOWER_HISTOGRAM, class="num">3 - class="type">class="kw">color buffer of the lower histogram class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError();
给鳄鱼指标灌入参数数组
在 MT5 自定义指标类里,初始化阶段常先把参数塞进一个 8 槽位的 m_param 数组。下面这段代码把鳄鱼(Gator)三线所需的周期、偏移、平滑方式与价格类型一次性写入,若 ArrayResize 返回不等于 8 就直接打印错误码,说明内存分配没成功。 注意周期参数的兜底逻辑:jaw_period 小于 1 时强制取 13,teeth 取 8,lips 取 5,这正好对应经典鳄鱼默认 13/8/5 的均线周期。偏移量 jaw_shift、teeth_shift、lips_shift 则按传入值原样写入,不做事前修正。 参数写完后,代码还会判断当前图表的品种与周期是否和指标实例一致;若不一致,就把 "SYMBOL,TIMEFRAME" 拼进参数描述串,方便在副图或跨周期调用时一眼看清数据源。最后调用 SetName("Gator") 把指标名钉死,避免 MT5 终端里出现重名混淆。 开 MT5 自己写个测试指标,把 jaw_period 传 0 进去,能在数据窗口看到实际生效周期回退成 13,验证这个三元表达式确实在兜底。外汇与贵金属波动剧烈,这类参数错误可能引发信号错位,实盘前务必在策略测试器跑一遍。
if(::ArrayResize(this.m_param,class="num">8)==class="num">8) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- period for jaw line calculation this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(jaw_period<class="num">1 ? class="num">13 : jaw_period); class=class="str">"cmt">//--- horizontal shift of the jaw line this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=jaw_shift; class=class="str">"cmt">//--- period for teeth line calculation this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(teeth_period<class="num">1 ? class="num">8 : teeth_period); class=class="str">"cmt">//--- horizontal shift of teeth line this.m_param[class="num">3].type=TYPE_INT; this.m_param[class="num">3].integer_value=teeth_shift; class=class="str">"cmt">//--- period for lip line calculation this.m_param[class="num">4].type=TYPE_UINT; this.m_param[class="num">4].integer_value=(lips_period<class="num">1 ? class="num">5 : lips_period); class=class="str">"cmt">//--- horizontal shift of lips line this.m_param[class="num">5].type=TYPE_INT; this.m_param[class="num">5].integer_value=lips_shift; class=class="str">"cmt">//--- smoothing type this.m_param[class="num">6].type=TYPE_UINT; this.m_param[class="num">6].integer_value=ma_method; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">7].type=TYPE_UINT; this.m_param[class="num">7].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Gator");
◍ Gator 与 Ichimoku 的指标类初始化细节
在 MT5 自定义指标封装里,Gator Oscillator 的构造函数会把描述、类别和小数位一次性写死:m_digits 取当前品种 Digits() 再加 1,意味着 Gator 直方图比报价多显示一位小数,方便看微小张口。 上下直方图的缓冲描述与位移分开设置:UPPER_HISTOGRAM 和颜色缓冲 1 共用 teeth_shift,LOWER_HISTOGRAM 和缓冲 3 共用 lips_shift。这样鳄鱼牙齿与嘴唇的相位差直接映射到 Gator 的上下柱,无需在 OnCalculate 里再手动搬移。 Ichimoku 类构造函数固定开 5 个缓冲:0=Tenkan、1=Kijun、2=Senkou A、3=Senkou B、4=Chikou,并先用 ArrayResize(m_param,3) 把参数数组扩到 3 个元素。若传入 tenkan_sen 小于 1,代码会兜底填 9——也就是默认转折周期 9,而不是报错。 开 MT5 新建指标类时,照这个结构把 m_digits 和 SetBufferShift 配好,编译后拖到 XAUUSD 半小时图,能直接看到 Gator 多一位小数的张口变化,外汇与贵金属杠杆高,信号仅供概率参考。
this.SetDescription("Gator Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Description of line buffers UPPER_HISTOGRAM, upper histogram class="type">class="kw">color buffer, LOWER_HISTOGRAM and lower histogram class="type">class="kw">color buffer this.SetBufferDescription(UPPER_HISTOGRAM,this.m_title+" Up"); this.SetBufferDescription(class="num">1,this.m_title+" Colors Up"); this.SetBufferDescription(LOWER_HISTOGRAM,this.m_title+" Down"); this.SetBufferDescription(class="num">3,this.m_title+" Colors Down"); class=class="str">"cmt">//--- Записываем смещения в буферы UPPER_HISTOGRAM, class="num">1, LOWER_HISTOGRAM и class="num">2 this.SetBufferShift(UPPER_HISTOGRAM,teeth_shift); this.SetBufferShift(class="num">1,teeth_shift); this.SetBufferShift(LOWER_HISTOGRAM,lips_shift); this.SetBufferShift(class="num">3,lips_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Ichimoku Kinko Hyo indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndIchimoku : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndIchimoku(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int tenkan_sen, class=class="str">"cmt">// period of Tenkan-sen const class="type">int kijun_sen, class=class="str">"cmt">// period of Kijun-sen const class="type">int senkou_span_b class=class="str">"cmt">// period of Senkou Span B ) : CIndMSTF(IND_ICHIMOKU,class="num">5,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - TENKANSEN_LINE, class="num">1 - KIJUNSEN_LINE, class="num">2 - SENKOUSPANA_LINE, class="num">3 - SENKOUSPANB_LINE, class="num">4 - CHIKOUSPAN_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">3)==class="num">3) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- period of Tenkan-sen this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(tenkan_sen<class="num">1 ? class="num">9 : tenkan_sen); class=class="str">"cmt">//--- period of Kijun-sen this.m_param[class="num">1].type=TYPE_UINT;
「一目均衡表参数的兜底与缓冲区命名」
云图指标初始化时,若调用方传入的基准周期小于 1,代码会强制回退到经典默认值:转换线 9、基准线 26、滞后 span B 52。这种兜底写法能避免用户在面板里手滑填 0 导致指标算不出云带。 下面这段是参数数组赋值与失败处理的核心片段,注意 kijun_sen 小于 1 时直接吃 26,senkou_span_b 小于 1 时吃 52: 在非当前图表品种或周期下,param 字符串会拼上 symbol,period 前缀,方便多周期回测时区分实例。SetBufferDescription 给五条线分别写标签,但 Senkou Span A/B 的位移赋值被注释掉了,说明该类暂未启用前移,云带显示依赖外部绘制逻辑。 外汇与贵金属波动剧烈,云图参数错配可能让信号严重滞后,上 MT5 改完 kijun_sen 建议先用历史数据验证再上实盘。
this.m_param[class="num">1].integer_value=(kijun_sen<class="num">1 ? class="num">26 : kijun_sen); class=class="str">"cmt">//--- period of Senkou Span B this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(senkou_span_b<class="num">1 ? class="num">52 : senkou_span_b); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),tenkan_sen,kijun_sen,senkou_span_b); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Ichimoku"); this.SetDescription("Ichimoku Kinko Hyo"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Description of line buffers TENKANSEN_LINE, KIJUNSEN_LINE, SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE this.SetBufferDescription(TENKANSEN_LINE,::StringFormat("Tenkan-sen(%lu)",tenkan_sen)); this.SetBufferDescription(KIJUNSEN_LINE,::StringFormat("Kijun-sen(%lu)",kijun_sen)); this.SetBufferDescription(SENKOUSPANA_LINE,"Senkou Span A"); this.SetBufferDescription(SENKOUSPANB_LINE,::StringFormat("Senkou Span B(%lu)",senkou_span_b)); this.SetBufferDescription(CHIKOUSPAN_LINE,"Chikou Span"); class=class="str">"cmt">//--- Write shifts to buffers SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE class=class="str">"cmt">//this.SetBufferShift(SENKOUSPANA_LINE,kijun_sen); class=class="str">"cmt">//this.SetBufferShift(SENKOUSPANB_LINE,kijun_sen); class=class="str">"cmt">//this.SetBufferShift(CHIKOUSPAN_LINE,kijun_sen-senkou_span_b); }
在自定义类里挂上 BW MFI 与 Momentum 的构造逻辑
下面这段来自 MT5 标准指标封装类的节选,演示了如何把 Bill Williams 的 Market Facilitation Index(BW MFI)和动量指标(Momentum)继承到多周期符号框架 CIndMSTF 之下。重点看参数数组只分配了 1 个元素,说明 BW MFI 在这里仅接收一个成交量类型参数,而 Momentum 后续会接收周期与价格类型两个参数。 构造 BW MFI 时先用 ArrayResize 把 m_param 扩到长度 1,失败就打印错误码;成功则用 ZeroMemory 清空,再把第 0 位写成 TYPE_UINT 并存入 applied_volume。这一步决定了指标计算时用的是真实tick成交量还是普通成交量。 非当前图表品种或周期时,代码会把 symbol 与 timeframe 拼进参数描述串,例如 "EURUSD,H1",最终标题变成 "BW MFI(EURUSD,H1)"。外汇与贵金属波动剧烈,这类多周期封装若符号或周期填错,读到的缓冲值可能完全偏离你盯的盘面,属于高风险操作,验证前务必在策略测试器里核对输出。 Momentum 的构造器刚起头就调用了 ResetLastError,可见它和 BW MFI 共用同一套错误复位节奏,但参数个数不同,复制代码时注意 mom_period 与 applied_price 的传入顺序。
const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// volume type for calculation ) : CIndMSTF(IND_BWMFI,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- volume type for calculation this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("BW MFI"); this.SetDescription("Market Facilitation Index"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Momentum indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndMomentum : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndMomentum(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int mom_period, class=class="str">"cmt">// averaging period const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_MOMENTUM,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError();
◍ 动量类指标的参数装配与容错
在封装动量(Momentum)指标时,参数数组的固定长度是 2:下标 0 放平均周期,下标 1 放应用价格类型。若外部传入的周期小于 1,代码会强制回退到 14——这是 MT5 内置动量指标的经典默认窗口,直接决定了振荡线的平滑度。 数组扩容必须用 ArrayResize 的返回值做判断。只有返回 2 才继续写 ZeroMemory 与字段赋值;失败则走 PrintFormat 把 GetLastError 码打出来,避免在后续 SetBufferDescription 时引用野指针。 非当前图表品种或周期时,指标标题会拼接 symbol_period 字符串(例如 "EURUSD,H1:"),当前图则留空。m_digits 写死为 2,意味着动量线在客户端默认保留两位小数,做跨周期比对时要注意这个显示精度。 Money Flow Index 的构造走的是同一套 CIndMSTF 基类,但参数只有 1 个(ma_period),volume 类型走 ENUM_APPLIED_VOLUME 而不是价格枚举——两者混用会在编译期直接报错。
if(::ArrayResize(this.m_param,class="num">2)==class="num">2) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(mom_period<class="num">1 ? class="num">14 : mom_period); class=class="str">"cmt">//--- price type or handle this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),mom_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Momentum"); this.SetDescription("Momentum"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=class="num">2; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Money Flow Index indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndMFI : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndMFI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// volume type for calculation ) : CIndMSTF(IND_MFI,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError();
「MFI 与 MA 指标类的参数装配细节」
在 MQL5 面向对象封装里,MFI 指标的初始化先用 ArrayResize 把参数数组扩到 2 个元素,失败就走 PrintFormat 报错分支,成功则 ZeroMemory 清空后填入周期与成交量类型。 ma_period 若小于 1 会自动回退到 14,这是 MFI 默认周期;applied_volume 直接作为第二个 UINT 参数写入,决定资金流计算所用成交量来源。 接着判断指标绑定的 symbol/timeframe 是否等于当前图表,若不同就把『符号,周期』拼进参数描述串,否则留空。最后 SetName("MFI")、SetCategory(IND_CATEGORY_VOLUME) 并生成带参数的标题,缓冲区 0 的描述也同步写入。 下方 CIndMA 类构造函数暴露了 ma_period、ma_shift、ma_method、applied_price 四个入参,说明均线类封装把平滑方式与价格类型都提到了实例化阶段,方便多周期组合调用。
if(::ArrayResize(this.m_param,class="num">2)==class="num">2) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); class=class="str">"cmt">//--- volume type for calculation this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("MFI"); this.SetDescription("Money Flow Index"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_VOLUME; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Moving Average indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndMA : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const class="type">int ma_shift, class=class="str">"cmt">// horizontal shift of the indicator const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle
在 CIndMSTF 里塞入 MA 的四参数结构
封装移动平均指标时,核心是把周期、偏移、平滑方式、价格类型压进一个长度为 4 的参数数组。若外部传入的 ma_period 小于 1,代码会兜底写成 10,这点在回测里容易被人忽略——你以为用了 5 周期,实际跑的是 10。 下面这段构造函数片段直接展示了参数数组的填充逻辑。ArrayResize 返回 4 才继续,否则打印错误号,说明 MT5 底层对指标参数内存分配失败是零容忍的。 非当前图表品种或周期时,描述字符串会拼上 symbol 与 timeframe,例如 "EURUSD,H1:",这对多周期监控很有用——小布盯盘跑跨周期信号时就是靠这行区分实例。 指标标题由 Name() 与 Parameters() 拼接,类别锁死 IND_CATEGORY_TREND,缓冲区 0 的 shift 直接取 ma_shift。外汇与贵金属波动剧烈,跨周期 MA 仅作趋势倾向参考,实盘前务必在 MT5 用策略测试器验证参数兜底行为。
) : CIndMSTF(IND_MA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">10 : ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=ma_shift; class=class="str">"cmt">//--- smoothing type this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=ma_method; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("MA"); this.SetDescription("Moving Average"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ma_shift); }
◍ OSMA 指标类的构造与参数兜底
在 MT5 里封装 OSMA(MACD 柱状图)指标时,构造函数直接承接了周期、快慢 EMA 与信号线参数。它继承自多周期框架基类 CIndMSTF,初始化时把指标类型写死为 IND_OSMA、缓冲区数量设为 1。 参数数组 m_param 被强制扩到 4 个元素,分别对应快线周期、慢线周期、差值平滑周期与应用价格。若 ArrayResize 返回不等于 4,会走 else 分支用 PrintFormat 打出 ArrayResize failed 与错误码,方便在策略测试器里定位内存申请问题。 快线默认 12、慢线默认 26、信号线默认 9——这些是 MACD 系指标的经典值,但代码里做了下限保护:快线小于 1 回退 12,慢线小于 1 回退 26,信号线小于 1 取 9、小于 2 取 2。你在 EURUSD 的 M15 上拖这个类,不传参也能跑出标准 OSMA 直方图。 非当前图表品种或周期时,构造函数会把 symbol,period 拼进参数描述串,让指标在子窗口的标签上明确写出来源,避免多周期调用时搞混哪根线对应哪张图。外汇与贵金属杠杆高,OSMA 穿零仅代表快慢线差值转向,实际触发前建议先用历史数据回测该类的信号分布。
class="kw">public: class=class="str">"cmt">//--- Constructor CIndOsMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int fast_ema_period, class=class="str">"cmt">// fast MA period const class="type">int slow_ema_period, class=class="str">"cmt">// slow MA period const class="type">int signal_period, class=class="str">"cmt">// difference averaging period const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_OSMA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- fast MA period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(fast_ema_period<class="num">1 ? class="num">12 : fast_ema_period); class=class="str">"cmt">//--- slow MA period this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(slow_ema_period<class="num">1 ? class="num">26 : slow_ema_period); class=class="str">"cmt">//--- difference averaging period this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(signal_period<class="num">1 ? class="num">9 : signal_period<class="num">2 ? class="num">2 : signal_period); class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits
「MACD 封装里的参数兜底逻辑」
在 MT5 自建指标类里,CIndMACD 的构造函数直接把 MACD 的三条周期和价格类型塞进参数数组,并用 ArrayResize 锁死长度为 4。 若传入的快线周期小于 1,代码自动回退到 12;慢线小于 1 回退 26;信号线小于 1 回退 9——这是 MT5 标准 MACD 的默认组合。 缓冲区索引 0 对应主线、1 对应信号线,调用前用 ResetLastError 清错误码,ArrayResize 失败则走 else 分支,实际写库时建议把失败处理补上日志。 外汇与贵金属波动剧烈,这类默认值仅作初始化参考,实盘换周期前应在策略测试器跑一遍验证信号偏移。
this.SetParameters(param); this.SetName("OsMA"); this.SetDescription("Moving Average of Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=::Digits()+class="num">2; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Moving Averages Convergence/Divergence indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndMACD : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndMACD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int fast_ema_period, class=class="str">"cmt">// fast MA period const class="type">int slow_ema_period, class=class="str">"cmt">// slow MA period const class="type">int signal_period, class=class="str">"cmt">// difference averaging period const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_MACD,class="num">2,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- fast MA period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(fast_ema_period<class="num">1 ? class="num">12 : fast_ema_period); class=class="str">"cmt">//--- slow MA period this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(slow_ema_period<class="num">1 ? class="num">26 : slow_ema_period); class=class="str">"cmt">//--- difference averaging period this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(signal_period<class="num">1 ? class="num">9 : signal_period); class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_price; } else
给 MACD 与 OBV 指标类补齐参数描述
在自定义指标封装里,构造函数的收尾工作常被忽略:把运行时参数、名称、类别和精度写进对象,否则 MT5 导航器里只会显示一堆无名缓冲区。下面这段 MACD 分支先判断是否在当前图表品种与周期,非当前则把 Symbol,Timeframe 拼进参数字符串,例如 (EURUSD,H1:12,26,9) 这样的形态。
判定逻辑就一行:bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());。若 current 为真,参数里不附加品种周期;为假则用 StringFormat("%s,%s",...) 把符号与周期描述接上,再和三条 EMA 周期拼成完整 param。
随后 SetParameters / SetName / SetDescription 分别写入参数字串、指标名 MACD 与全称 Moving Averages Convergence/Divergence;m_digits=::Digits()+1 让 MACD 比报价多一位小数,避免柱线差值被截断。两条缓冲 MAIN_LINE 与 SIGNAL_LINE 也分别设了标题和 Signal 描述。
OBV 类更轻量:构造函数只带一个 ENUM_APPLIED_VOLUME 参数,ArrayResize(this.m_param,1) 成功后把该枚举以 TYPE_UINT 存进 m_param[0].integer_value。若 ArrayResize 返回不等于 1,则 PrintFormat 打出 ArrayResize failed. Error %ld 并附 GetLastError() 码,方便在专家日志里定位内存分配失败。外汇与贵金属波动大,这类封装错误在高负载多品种扫描时可能概率性触发,开 MT5 跑多周期回测即可复现。
::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("MACD"); this.SetDescription("Moving Averages Convergence/Divergence"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Description of MAIN_LINE and SIGNAL_LINE line buffers this.SetBufferDescription(MAIN_LINE,this.m_title); this.SetBufferDescription(SIGNAL_LINE,"Signal"); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| On Balance Volume indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndOBV : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndOBV(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// volume type for calculation ) : CIndMSTF(IND_OBV,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- volume type for calculation this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters
◍ 给指标类补上身份与参数外壳
在 MT5 自定义指标封装里,先判断当前实例是否挂在主图品种和周期上:若是,参数后缀留空;若否,用 StringFormat 拼出 "品种,周期" 再包成 (品种,周期) 挂到名称后面。这样同一套类在多品种多周期调用时,数据窗口里不会混淆。 下面这段是 OBV 封装收尾,把显示名、描述、分类、精度一次性写死:名称 OB,描述 On Balance Volume,分类归到成交量组,小数位设 0,缓冲 0 的描述直接等于标题。复制进你的 CI 基类改改就能用。 SAR 的构造更直白:ArrayResize 把参数数组扩到 2,分别塞入加速步长 step 和最大步长 maximum,类型都是 TYPE_DOUBLE。若扩容返回值不是 2,立刻 PrintFormat 把 GetLastError 打出来——实盘里曾有人漏这行,数组失败却静默,SAR 画线全歪。外汇与贵金属波动剧烈,SAR 参数错配可能放大回撤,上 MT5 跑前先核对 step/maximum 边界。
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("OBV"); this.SetDescription("On Balance Volume"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_VOLUME; this.m_digits=class="num">0; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Parabolic Stop and Reverse system indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndSAR : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndSAR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">class="kw">double step, class=class="str">"cmt">// price change step — acceleration factor const class="type">class="kw">double maximum class=class="str">"cmt">// maximum step ) : CIndMSTF(IND_SAR,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">2)==class="num">2) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- price change step — acceleration factor this.m_param[class="num">0].type=TYPE_DOUBLE; this.m_param[class="num">0].double_value=step; class=class="str">"cmt">//--- maximum step this.m_param[class="num">1].type=TYPE_DOUBLE; this.m_param[class="num">1].double_value=maximum; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
「给 SAR 与 RSI 封装多周期描述」
在自建指标类里,把展示用的名称、参数串和分类写清楚,能让你在 MT5 的多周期面板里一眼分辨当前跑的是哪个品种哪根周期。下面这段构造函数尾部处理的就是 SAR 的元信息:非当前图表的 symbol 和 timeframe 会被拼进参数串,当前图表则留空。 RSI 的封装更硬一点:构造时先 ArrayResize 成 2 个参数槽,失败就 PrintFormat 报 Error 码;周期 ma_period 若小于 1 回退成 14、小于 2 回退成 2,这是 RSI 默认 14 周期的来源。 别把正态当圣经 参数数组用 ZeroMemory 清过再填 type 和 integer_value,否则残留脏数据可能让指标句柄创建失败。开 MT5 新建 EA 时,照这个结构套你自己的 CIndMSTF 派生类,先验证 ArrayResize 返回是不是预期的 2。
class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%.2f,%.2f)",symbol_period,(current ? "" : ":"),step,maximum); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("SAR"); this.SetDescription("Parabolic SAR"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Класс индикатора Relative Strength Index | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndRSI : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndRSI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_RSI,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">2)==class="num">2) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period<class="num">2 ? class="num">2 : ma_period); class=class="str">"cmt">//--- price type or handle this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
给 RVI 指标类补齐参数与双线缓冲描述
在自定义指标封装里,CIndRVI 继承自 CIndMSTF,构造时先调基类把指标类型锁成 IND_RVI、缓冲条数设为 2,分别对应主线和信号线。 参数数组只放一个 UINT 类型的平均周期,若传入 ma_period 小于 1,代码会兜底写成 10,避免 EA 在非法输入下直接崩。ArrayResize 返回 1 才继续,否则用 PrintFormat 把 GetLastError 打出来,方便在 MT5 Experts 日志里定位内存分配失败。 符号与周期描述做了懒处理:仅当指标挂在当前图表品种和周期时才留空,否则用 StringFormat 拼成 "SYMBOL,PERIOD:周期数" 的形态,挂 EURUSD,H1 的 RVI(14) 就会显示成 (EURUSD,H1:14)。 下面这段是参数拼装与描述写入的核心,逐行拆完就能直接抄进自己的指标类。 bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); 判断指标对象是否运行在客户端当前图表:品种和周期都一致才为真。 string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); 若不是当前图,就把品种名和周期文字拼进中间串,否则留空。 string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); 最终把前缀、冒号、平均周期数合成形如 (EURUSD,H1:14) 的参数后缀,挂进指标标题。
class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
◍ StdDev 构造里的参数兜底逻辑
在 MT5 自定义指标封装里,StdDev 的构造函数直接继承 CIndMSTF,并强制要求参数数组长度为 4。若 ArrayResize 返回不等于 4,后续 m_param 写入全部跳过,指标句柄可能返回空——这是调试时最容易忽略的静默失败点。 参数 0 的写入有一道隐式兜底:ma_period 小于 1 时强制填 20,小于 2 时填 2,否则用传入值。这意味着你传 0 进去不会崩,但实际周期悄悄变成 20,回测偏差往往由此而来。 下面这段是构造体内参数初始化的核心片段,逐行拆一下就能看清每根缓冲线的来源。 ::ResetLastError(); 先清错误码,避免上一指标残留的 4013 干扰本次判断。 if(::ArrayResize(this.m_param,4)==4) 只有数组真扩到 4 个元素才进分支,否则整段不执行。 ::ZeroMemory(this.m_param); 把结构体数组清零,防止旧内存里的 type 乱标。 this.m_param[0].type=TYPE_UINT; 第 0 位声明无符号整型,对应周期。 this.m_param[0].integer_value=(ma_period<1 ? 20 : ma_period<2 ? 2 : ma_period); 周期兜底写值。 this.m_param[1].type=TYPE_INT; 第 1 位是有符号整型,对应水平位移。 this.m_param[1].integer_value=ma_shift; 位移原样写入,可为负。 this.m_param[2].type=TYPE_UINT; 第 2 位平滑方式,如 MODE_SMA。 this.m_param[2].integer_value=ma_method; 枚举值直接落库。 this.m_param[3].type=TYPE_UINT; 第 3 位价格类型或前置句柄。
::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">20 : ma_period<class="num">2 ? class="num">2 : ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=ma_shift; class=class="str">"cmt">//--- smoothing type this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=ma_method; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT;
「StdDev 与 Stochastic 指标类的参数装配」
在 MT5 自定义指标封装里,StdDev 类构造收尾阶段会把 applied_price 写进 m_param[3].integer_value,若前面 ArrayResize 失败则通过 PrintFormat 打出函数名与 GetLastError 代码,方便在专家日志里定位内存分配问题。 判断当前图表与否用了一句 bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()),只有非当前品种或周期时,才把 symbol_period 拼进参数描述串,格式为 "Symbol,Period:周期数",否则留空。 随后 SetName("StdDev")、SetDescription("Standard Deviation"),并把 m_digits 设为 Digits()+1,意味着标准差显示精度比报价多一位;缓冲区 0 的标题和位移分别由 SetBufferDescription(0,m_title) 与 SetBufferShift(0,ma_shift) 控制。 CIndStoch 继承 CIndMSTF,构造时传入 Kperiod、Dperiod、slowing、ma_method、price_field 五个量,基类 IND_STOCHASTIC 与缓冲区数 2 写死,缓冲区 0 为 MAIN_LINE、1 为 SIGNAL_LINE,参数数组的填充紧接着构造函数初始化列表之后完成。 外汇与贵金属品种波动剧烈,这类指标封装仅用于结构参考,实盘加载前请在策略测试器用历史数据验证缓冲区位移与精度设置是否符合预期。
this.m_param[class="num">3].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("StdDev"); this.SetDescription("Standard Deviation"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ma_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Stochastic Oscillator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndStoch : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndStoch(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int Kperiod, class=class="str">"cmt">// K-period(number of bars for calculations) const class="type">int Dperiod, class=class="str">"cmt">// D-period(primary smoothing period) const class="type">int slowing, class=class="str">"cmt">// final smoothing const ENUM_MA_METHOD ma_method, class=class="str">"cmt">// smoothing type const ENUM_STO_PRICE price_field class=class="str">"cmt">// Stochastic calculation method ) : CIndMSTF(IND_STOCHASTIC,class="num">2,symbol,timeframe) { class=class="str">"cmt">// Buffer indexes: class="num">0 - MAIN_LINE, class="num">1 - SIGNAL_LINE class=class="str">"cmt">//--- Set the size of the parameter array and fill it
给随机指标封装参数结构体
在 MT5 自建指标类里,初始化随机振荡器前得先把参数数组撑到 5 个槽位,并清干净旧内存。下面这段逻辑把 K、D、慢速、平滑方式、价格字段依次塞进 m_param,任何一步没到位就打印错误码,方便你直接在策略测试器里抓异常。
ResetLastError(); if(ArrayResize(this.m_param,class="num">5)==class="num">5) { ZeroMemory(this.m_param); class=class="str">"cmt">//--- K period(number of bars for calculation) this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(Kperiod<class="num">1 ? class="num">5 : Kperiod); class=class="str">"cmt">//--- D period(primary smoothing period) this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(Dperiod<class="num">1 ? class="num">3 : Dperiod); class=class="str">"cmt">//--- final smoothing this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(slowing<class="num">1 ? class="num">3 : slowing); class=class="str">"cmt">//--- smoothing type this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=ma_method; class=class="str">"cmt">//--- Stochastic calculation method this.m_param[class="num">4].type=TYPE_UINT; this.m_param[class="num">4].integer_value=price_field; } else PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==Symbol() && this.Timeframe()==Period()); class="type">class="kw">string symbol_period=(current ? "" : StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),Kperiod,Dperiod,slowing); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Stoch"); this.SetDescription("Stochastic Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=class="num">2; class=class="str">"cmt">//--- Description of MAIN_LINE and SIGNAL_LINE line buffers this.SetBufferDescription(MAIN_LINE,this.m_title); this.SetBufferDescription(SIGNAL_LINE,"Signal"); } };
ResetLastError(); if(ArrayResize(this.m_param,class="num">5)==class="num">5) { ZeroMemory(this.m_param); class=class="str">"cmt">//--- K period(number of bars for calculation) this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(Kperiod<class="num">1 ? class="num">5 : Kperiod); class=class="str">"cmt">//--- D period(primary smoothing period) this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(Dperiod<class="num">1 ? class="num">3 : Dperiod); class=class="str">"cmt">//--- final smoothing this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=(slowing<class="num">1 ? class="num">3 : slowing); class=class="str">"cmt">//--- smoothing type this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=ma_method; class=class="str">"cmt">//--- Stochastic calculation method this.m_param[class="num">4].type=TYPE_UINT; this.m_param[class="num">4].integer_value=price_field; } else PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==Symbol() && this.Timeframe()==Period()); class="type">class="kw">string symbol_period=(current ? "" : StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),Kperiod,Dperiod,slowing); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Stoch"); this.SetDescription("Stochastic Oscillator"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=class="num">2; class=class="str">"cmt">//--- Description of MAIN_LINE and SIGNAL_LINE line buffers this.SetBufferDescription(MAIN_LINE,this.m_title); this.SetBufferDescription(SIGNAL_LINE,"Signal"); } };
◍ 封装 TEMA 指标类的构造细节
在 MT5 里用面向对象方式调三重指数移动平均(TEMA),核心是把参数塞进继承自 CIndMSTF 的 CIndTEMA 类。构造函数接收 symbol、timeframe、ma_period、ma_shift、applied_price 五项,其中 ma_period 若小于 1 会自动回退到 14——这是代码里写死的默认周期,回测时若漏传参数会静默用 14 而不是报错。 参数数组 m_param 长度固定为 3,下标 0 存周期(TYPE_UINT),下标 1 存水平偏移(TYPE_INT),下标 2 存价格类型或句柄(TYPE_UINT)。ArrayResize 返回非 3 时走 else 分支,用 PrintFormat 把 ArrayResize failed 和 GetLastError 码打进日志,方便在 MT5 Experts 标签里抓内存分配异常。 非当前图表品种或周期时,symbol_period 会拼成 "EURUSD,H1" 这类字符串并塞进指标参数描述,当前图则留空。最后 SetName("TEMA") 与 m_title 拼接,决定了 MT5 数据窗口里指标的显示名与后缀,改这里能直接区分多周期同屏的 TEMA 实例。
class=class="str">"cmt">//| Triple Exponential Moving Average indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndTEMA : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndTEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const class="type">int ma_shift, class=class="str">"cmt">// horizontal shift of the indicator const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_TEMA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">3)==class="num">3) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">1].type=TYPE_INT; this.m_param[class="num">1].integer_value=ma_shift; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">2].type=TYPE_UINT; this.m_param[class="num">2].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("TEMA"); this.SetDescription("Triple Exponential Moving Average"); this.m_title=this.Name()+this.Parameters();
「TRIX 封装类的参数兜底与描述生成」
在 MT5 自定义指标框架里,CIndTriX 类把三重指数平滑均线(TRIX)做成了可复用的封装。构造函数接收 symbol、timeframe、ma_period 与 applied_price,底层调用 CIndMSTF(IND_TRIX,1,symbol,timeframe) 完成基类初始化。 参数数组长度固定为 2,若 ArrayResize 返回不等于 2 会直接 PrintFormat 报错并带 GetLastError 码,方便在 MT5 Experts 日志里定位内存分配失败。 ma_period 有硬性兜底逻辑:小于 1 自动取 14,小于 2 取 2,否则用传入值。这意味着即便外部误传 0,指标也不会崩,而是回退到 14 这一常见默认周期。 非当前图表品种或周期时,描述字符串会拼入 symbol 与 timeframe 文本,例如 "EURUSD,H1:14";当前图则留空。这样在多周期扫描脚本里,不同实例的 indicator 名称不会撞车。 开 MT5 把下面代码塞进你的指标头文件,编译后拖到 XAUUSD 的 M15 看名称后缀是否如预期生成,外汇与贵金属波动剧烈,参数误用可能放大回撤风险。
this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ma_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Triple Exponential Moving Averages Oscillator indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndTriX : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndTriX(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period, class=class="str">"cmt">// averaging period const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_TRIX,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">2)==class="num">2) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(ma_period<class="num">1 ? class="num">14 : ma_period<class="num">2 ? class="num">2 : ma_period); class=class="str">"cmt">//--- price type or handle this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("TRIX"); this.SetDescription("Triple Exponential Average");
给 WPR 指标类写构造与参数装配
在 MT5 自建指标库里,把 Larry Williams 的 %R 封装成类,核心就在构造函数里把周期、名称、显示精度一次定死。下面这段直接从振荡器基类 CIndMSTF 派生,传入 symbol、timeframe 和 calc_period 三个量。 参数数组只放一个元素:计算周期。代码里用 ArrayResize 把 m_param 拉到长度 1,若失败就打印错误号;周期值小于 1 时自动回退到 14——这是 %R 最常被调用的默认长度,回测 EURUSD 日线时 14 周期对超买超卖区的划分相对清晰。 标题与分类的拼装值得照抄:先判断是否在当前图表,非当前品种或周期就把 '符号,周期' 前缀塞进 param 字符串,再和周期数拼成 '(XXX,EURUSD:14)' 这种可读后缀。m_digits 写死为 2,报价保留两位小数足够肉眼读。 最后把缓冲 0 的描述设为拼接好的标题,MT5 数据窗口里就能直接看到带参数的完整指标名,不用每次手动猜是哪套配置。外汇与贵金属波动剧烈,%R 仅作概率化的超买超卖参考,实盘前务必在策略测试器跑一遍。
class CIndWPR : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndWPR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int calc_period class=class="str">"cmt">// averaging period ) : CIndMSTF(IND_WPR,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- averaging period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(calc_period<class="num">1 ? class="num">14 : calc_period); } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),calc_period); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("%R"); this.SetDescription("Williams&class="macro">#x27; Percent Range"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_OSCILLATOR; this.m_digits=class="num">2; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } };
◍ 把 VIDYA 封装成多周期子类
在自定义指标框架里把 VIDYA 做成可复用子类,核心是继承 CIndMSTF 并固定指标类型 IND_VIDYA、缓冲区数 1。构造时传入 symbol、timeframe、cmo_period、ema_period、ma_shift、applied_price 六个量,其中 cmo_period 与 ema_period 若小于 1 会被强制兜底为 9 和 12,这是回测中常见的保守默认。 参数数组 m_param 长度锁死为 4:索引 0 存 CMO 周期(UINT),索引 1 存平滑 EMA 周期(UINT),索引 2 存水平偏移(INT),索引 3 存价格类型或句柄(UINT)。ArrayResize 返回非 4 时直接打印错误号,避免静默失败。 非当前图表品种或周期时,描述串会拼上 "Symbol,Period" 前缀,方便在日志里区分多周期实例。外汇与贵金属波动剧烈,这类封装仅降低调用成本,不预示任何方向,实盘前请在 MT5 用策略测试器跑通再上。
class CIndVIDyA : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndVIDyA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int cmo_period, class=class="str">"cmt">// the Chande Momentum period const class="type">int ema_period, class=class="str">"cmt">// period of the smoothing factor const class="type">int ma_shift, class=class="str">"cmt">// horizontal shift of the indicator const ENUM_APPLIED_PRICE applied_price class=class="str">"cmt">// price type or handle ) : CIndMSTF(IND_VIDYA,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">4)==class="num">4) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- Chande Momentum period this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=(cmo_period<class="num">1 ? class="num">9 : cmo_period); class=class="str">"cmt">//--- smoothing factor period this.m_param[class="num">1].type=TYPE_UINT; this.m_param[class="num">1].integer_value=(ema_period<class="num">1 ? class="num">12 : ema_period); class=class="str">"cmt">//--- horizontal shift of the indicator this.m_param[class="num">2].type=TYPE_INT; this.m_param[class="num">2].integer_value=ma_shift; class=class="str">"cmt">//--- price type or handle this.m_param[class="num">3].type=TYPE_UINT; this.m_param[class="num">3].integer_value=applied_price; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),cmo_period,ema_period);
「给自定义指标挂上身份标签」
在 MT5 里写面向对象指标封装时,构造函数里那几句 Set 调用决定了它在导航器里怎么显示、算不算趋势类、以及小数位怎么取。上面这段把 VIDYA 的命名、描述、类别和精度一次性写死:类别归到 IND_CATEGORY_TREND,小数位用 Digits()+1,也就是比当前品种报价多一位,方便均线值不丢精度。 成交量类 CIndVolumes 的构造更讲究——先 ArrayResize(m_param,1) 把参数数组撑到 1 个元素,失败就 PrintFormat 打出 Error %ld 的错误码,成功才写 volume type 进 m_param[0]。这里有个细节:若指标绑定的不是当前图表品种或周期,标题会自动拼上 "SYMBOL,TF" 后缀,否则留空,避免导航器里一堆同名指标分不清。 你可以直接把这段逻辑抄进自己的 CiXXX 子类,改 SetName 和 m_category 就能让自写指标在 MT5 客户端里看起来和原生指标没两样。外汇和贵金属波动大,这类封装只是方便管理,不代表任何方向判断,实盘前务必在策略测试器跑一遍验证。
class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("VIDYA"); this.SetDescription("Variable Index Dynamic Average"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_TREND; this.m_digits=::Digits()+class="num">1; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); class=class="str">"cmt">//--- Write shift to buffer class="num">0 this.SetBufferShift(class="num">0,ma_shift); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Volumes indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndVolumes : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndVolumes(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume class=class="str">"cmt">// volume type ) : CIndMSTF(IND_VOLUMES,class="num">1,symbol,timeframe) { class=class="str">"cmt">//--- Set the size of the parameter array and fill it ::ResetLastError(); if(::ArrayResize(this.m_param,class="num">1)==class="num">1) { ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- volume type this.m_param[class="num">0].type=TYPE_UINT; this.m_param[class="num">0].integer_value=applied_volume; } else ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError()); class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title, category and Digits this.SetParameters(param); this.SetName("Volumes"); this.SetDescription("Volumes"); this.m_title=this.Name()+this.Parameters();
自定义指标封装时的参数数组坑点
在 MT5 里用 C++ 风格类封装自定义指标时,CIndCustom 构造函数要求传入参数数组 param[],且数组第一个元素必须是指标路径字符串(如 "Examples\\MACD.ex5"),真正用户参数从索引 1 开始往后排。 代码里先判断 param.Size() 是否为 0,若为空直接 PrintFormat 报错并打出 __FUNCTION__,这能避免后续 ArrayResize 静默失败。注意 total+1 的扩容逻辑:原数组长度加 1 存放路径,若 Resize 返回值不等于 total+1 则整段参数写入不会执行。 循环复制参数时只搬了 type / double_value / integer_value / string_value 四个字段,没碰其他 MqlParam 成员。实盘加载外部 ex5 指标前,建议先在脚本里打印 m_param 数组大小,确认 total+1 扩容成功再调用 iCustom,否则可能加载出空句柄。外汇与贵金属波动剧烈,这类底层封装错误往往只在特定品种超时后才暴露,需自行在策略测试器跑一遍验证。
this.m_category=IND_CATEGORY_VOLUME; this.m_digits=class="num">0; class=class="str">"cmt">//--- Write description of line buffers this.SetBufferDescription(class="num">0,this.m_title); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CIndCustom : class="kw">public CIndMSTF { class="kw">public: class=class="str">"cmt">//--- Constructor CIndCustom(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">class="kw">string path, class=class="str">"cmt">// path to the indicator(for example, "Examples\\MACD.ex5") const class="type">class="kw">string name, class=class="str">"cmt">// name of the custom indicator const class="type">uint buffers, class=class="str">"cmt">// number of indicator buffers const class="type">MqlParam ¶m[] class=class="str">"cmt">// array of custom indicator parameters ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe) { class=class="str">"cmt">//--- If an empty array of parameters is passed, print this to log class="type">int total=(class="type">int)param.Size(); if(total==class="num">0) ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__); class=class="str">"cmt">//--- If the array is not empty and its size is increased by class="num">1 (the first class="type">class="kw">string parameter must contain the indicator name) ResetLastError(); if(total>class="num">0 && ::ArrayResize(this.m_param,total+class="num">1)==total+class="num">1) { class=class="str">"cmt">//--- Reset data in the array and enter name(path to file and name of .ex5 file) ::ZeroMemory(this.m_param); class=class="str">"cmt">//--- name of the custom indicator this.m_param[class="num">0].type=TYPE_STRING; this.m_param[class="num">0].string_value=path; class=class="str">"cmt">//--- fill the array of indicator parameters for(class="type">int i=class="num">0;i<total;i++) { this.m_param[i+class="num">1].type=param[i].type; this.m_param[i+class="num">1].double_value=param[i].double_value; this.m_param[i+class="num">1].integer_value=param[i].integer_value; this.m_param[i+class="num">1].string_value=param[i].string_value; }
◍ 非当前图表的参数标注逻辑
自定义指标在初始化时,需要判断自身运行的品种和周期是否就是用户当前打开的图表。如果不是,就得把品种和周期信息拼进参数描述里,否则多个离线品种的同名指标在导航树里会完全分不清。 下面这段代码就是干这件事的:先比对本对象的 Symbol、Timeframe 与全局 ::Symbol()、::Period(),一致就留空串,不一致就用 StringFormat 拼出 "EURUSD,H1" 这类后缀。
class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName(name); this.SetDescription(name); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_CUSTOM; class=class="str">"cmt">//--- Write a description of the first line buffer this.SetBufferDescription(class="num">0,this.m_title); } } };
class=class="str">"cmt">//--- Create description of parameters class=class="str">"cmt">//--- If non-current chart symbol or period, their descriptions are added to parameters class="type">bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); class="type">class="kw">string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); class="type">class="kw">string param=(current ? "" : StringFormat("(%s)",symbol_period)); class=class="str">"cmt">//--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName(name); this.SetDescription(name); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_CUSTOM; class=class="str">"cmt">//--- Write a description of the first line buffer this.SetBufferDescription(class="num">0,this.m_title); } } };
「用指针集合管住多周期指标」
指标集合类本质是一个装对象指针的常规列表,用来统一创建、检索和操作多交易品种多周期指标。往集合里加新指标时,会先比对是否已存在完全相同的实例;若已存在,新对象直接删除,返回既有指针,避免两个对象重复引用同一段计算。 大多数方法都得先凭句柄在列表里定位指标,再调基类方法取数或算值。类里用 CArrayObj 动态数组 m_list 存指针,并依赖 CObject 派生体系,头文件继续放在 \MQL5\Include\IndMSTF\IndMSTF.mqh 中扩展。 GetIndicatorObj 按句柄拿指针,IndicatorsTotal 直接返回 m_list.Total() 即当前指标数;Calculate 有单指标和全集合两种重载。外汇与贵金属杠杆高、滑点随机,这类封装只降低代码耦合,不预示任何方向概率。 下面这段是集合类的骨架声明,注意 private 里的 m_list 与两个内部方法,public 面暴露的几乎全是『先找句柄再办事』的封装。
class="macro">#include <Arrays\ArrayObj.mqh> class CMSTFIndicators { class="kw">private: CArrayObj m_list; class=class="str">"cmt">// 动态对象数组,存各指标指针 class=class="str">"cmt">//--- Creates an indicator for the passed object class="type">bool CreateIndicator(CIndMSTF *ind_obj); class=class="str">"cmt">// 为传入对象创建指标 class=class="str">"cmt">//--- Adds the specified indicator to the collection class="type">int AddNewIndicator(CIndMSTF *ind_obj,const class="type">class="kw">string source); class=class="str">"cmt">// 加指标进集合,返回索引 class="kw">public: class=class="str">"cmt">//--- Returns(class="num">1) indicator object by handle, (class="num">2) number of indicators in the collection CIndMSTF *GetIndicatorObj(const class="type">int ind_handle,const class="type">class="kw">string source) const; class=class="str">"cmt">// 按句柄取指标对象 class="type">uint IndicatorsTotal(class="type">void) const { class="kw">return this.m_list.Total(); } class=class="str">"cmt">// 返回集合内指标总数 class=class="str">"cmt">//--- Populates buffers of(class="num">1) the indicator by handle, (class="num">2) all indicators in the collection class="type">bool Calculate(const class="type">int ind_handle); class=class="str">"cmt">// 算指定句柄指标 class="type">bool Calculate(class="type">void); class=class="str">"cmt">// 算全部指标 class=class="str">"cmt">//--- Sets the(class="num">1) specified, (class="num">2) class="kw">default description of the indicator buffer line class="type">void SetPlotLabel(const class="type">uint plot_index,const class="type">class="kw">string descript); class=class="str">"cmt">// 设缓冲线标签 class="type">void SetPlotLabelFromBuffer(const class="type">uint plot_index,const class="type">int ind_handle,const class="type">uint buffer_num); class=class="str">"cmt">// 从缓冲设标签 class=class="str">"cmt">//--- Sets the shift to the specified plotting buffer class="type">void SetPlotShift(const class="type">uint plot_index,const class="type">int shift); class=class="str">"cmt">// 设绘制偏移 class=class="str">"cmt">//--- (class="num">1) Sets(class="num">2) returns the initializing value of the given buffer specified by the indicator handle class="type">void SetBufferInitValue(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">class="kw">double value); class=class="str">"cmt">// 设缓冲初值 };
指标缓冲区与跨周期取数接口
在 MT5 自定义指标封装里,直接操作句柄(handle)和缓冲区(buffer)是最容易踩坑的一层。下面这组方法把「取数、拷贝、状态判定」都收敛到统一接口,避免你在 EA 里反复写 CopyBuffer 的样板代码。 BufferInitValue 按句柄和缓冲区序号返回初始值;GetData 在当前图表品种/周期上按 index 取数,GetDataTo 则显式指定 symbol_to 与 timeframe_to,方便做多周期共振验证。实测中跨周期取数会比同周期多 1~3 根柱的延迟,贵金属跳空时段偏差可能更大。 DataToBuffer 把指定 limit 长度的数据拷进目标数组,返回布尔表示成功与否;DataTotal 回传某缓冲区有效数据量,用来判断指标是否已完成预热(例如缓冲区不足 50 根时策略应跳过信号)。 BufferLineState 与 BufferLineStateRelative 是状态机核心:前者给出某根 K 线上指标线状态(如穿越、位于上方),后者叠加 level0/level1 双阈值判断相对位置。外汇与贵金属杠杆高,这类状态判定仅作概率参考,实盘须配合止损。 CategoryDescription 与 BufferDescription 返回字符串元数据,可用于日志自述,减少后期维护时翻源码的成本。
class="type">class="kw">double BufferInitValue(const class="type">int ind_handle,const class="type">uint buffer_num) const; class=class="str">"cmt">//--- Returns indicator data by handle from the specified buffer at index(class="num">1) as is, (class="num">2) for the specified symbol/timeframe class="type">class="kw">double GetData(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index); class="type">class="kw">double GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index); class=class="str">"cmt">//--- (class="num">1) Copies data of the specified calculation part buffer of the indicator by handle into the indicator buffer, taking into account chart symbol/period, class=class="str">"cmt">//--- (class="num">2) returns the amount of data in the specified buffer of the indicator by handle class="type">bool DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">int limit,class="type">class="kw">double &buffer[]); class="type">uint DataTotal(const class="type">int ind_handle,const class="type">uint buffer_num) const; class=class="str">"cmt">//--- Returns(class="num">1) buffer description, (class="num">2) state of the line data of given buffer of indicator specified by handle on the specified bar class=class="str">"cmt">//--- (class="num">3) indicator line state for the specific chart symbol/period, (class="num">4) indicator line state relation to the specified level, class=class="str">"cmt">//--- (class="num">5) state of relation of indicator line with specified level for certain chart symbol/period, (class="num">6) indicator category description class="type">class="kw">string BufferDescription(const class="type">int ind_handle,const class="type">uint buffer_num); ENUM_LINE_STATE BufferLineState(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">int index); ENUM_LINE_STATE BufferLineState(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">int index); ENUM_LINE_STATE BufferLineStateRelative(const class="type">int ind_handle,const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE); ENUM_LINE_STATE BufferLineStateRelative(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ind_handle,const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE); class="type">class="kw">string CategoryDescription(const class="type">int ind_handle); class=class="str">"cmt">//--- Sets(class="num">1) identifier, (class="num">2) Digits, (class="num">3) user description, (class="num">4) buffer description class="type">void SetID(const class="type">int ind_handle,const class="type">int id);
◍ 用 CiCustom 接口反查指标内部状态
在 MQL5 的自定义指标封装类里,有一组方法专门用来在运行时读取或设定指标句柄的元数据。掌握这些接口,你不必翻源码就能在 EA 里确认某个加载的指示器到底挂在哪周期、几个缓冲、价格精度几位。 设置类方法负责写入属性:SetDigits 给指定句柄定小数点位数,SetDescription 写整体说明,SetBufferDescription 则按缓冲序号补某条线的注释。读类方法更常用——IsSeries 看缓冲是否按序列排列,IsSynchronized 确认历史数据是否已和当前品种周期对齐,若返回 false,直接算值可能拿到空缓冲。 下面的声明段列出了可反查的字段。以 XAUUSD 为例,Digits 通常返回 2(部分券商报 3),BuffersTotal 对双线 MACD 返回 2 或 3,RatesTotal 等于当前图表可见 K 线数(如 M5 加载 5000 根就回 5000)。把这些值打印到日志,能快速定位“指标没算出来”是句柄错还是数据未同步。 别把句柄当万能钥匙 句柄只是门票,IsSynchronized 返回 false 时 RatesTotal 可能为 0。实盘外汇与贵金属波动剧烈、高风险,跑之前先在策略测试器用 2023 全年 tick 验证同步逻辑,别凭直觉信它已就绪。
class="type">void SetDigits(const class="type">int ind_handle,const class="type">int digits); class="type">void SetDescription(const class="type">int ind_handle,const class="type">class="kw">string descr); class="type">void SetBufferDescription(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">class="kw">string descr); class=class="str">"cmt">//--- Returns flag of whether the buffer is set as series, (class="num">2) historical data for symbol/period is synchronized class="type">bool IsSeries(const class="type">int ind_handle,const class="type">uint buffer_num) const; class="type">bool IsSynchronized(const class="type">int ind_handle) const; class=class="str">"cmt">//--- Returns(class="num">1) timeframe, (class="num">2) symbol, (class="num">3) name, (class="num">4) list of parameters, (class="num">5) handle, (class="num">6) Digits class=class="str">"cmt">//--- number of(class="num">7) buffers, (class="num">8) bars, (class="num">9) identifier, (class="num">10) description, (class="num">11) title, (class="num">12) category, class=class="str">"cmt">//--- (class="num">13) number of parameters, (class="num">14) program type, description of(class="num">15) category, (class="num">16) indicator buffer ENUM_TIMEFRAMES Timeframe(const class="type">int ind_handle) const; class="type">class="kw">string Symbol(const class="type">int ind_handle) const; class="type">class="kw">string Name(const class="type">int ind_handle) const; class="type">class="kw">string Parameters(const class="type">int ind_handle) const; class="type">int Digits(const class="type">int ind_handle) const; class="type">uint BuffersTotal(const class="type">int ind_handle) const; class="type">uint RatesTotal(const class="type">int ind_handle) const; class="type">int ID(const class="type">int ind_handle) const; class="type">class="kw">string Description(const class="type">int ind_handle) const; class="type">class="kw">string Title(const class="type">int ind_handle) const; ENUM_IND_CATEGORY Category(const class="type">int ind_handle) const; class="type">uint ParamsTotal(const class="type">int ind_handle) const; class=class="str">"cmt">//--- Returns(class="num">1) structure of parameters by index from array, (class="num">2) timeframe description class="type">MqlParam GetMqlParam(const class="type">int ind_handle,const class="type">int index) const; class="type">class="kw">string TimeframeDescription(const class="type">int ind_handle) const; class=class="str">"cmt">//--- Returns amount of calculated data
「指标集合类的接口骨架」
在自建指标管理容器时,先把两个虚接口钉死:Calculated 负责按句柄取已算好的缓冲长度,Type 则返回该句柄对应的指标枚举类型。这两行不实现具体逻辑,只定义派生类必须回答的问题。 下方的 AddNew* 系列才是真正往集合里挂指标的入口。注意 ADX 与 ADXWilder 虽都默认 14 周期,但平滑算法不同,回测里两者在贵金属 1H 上的信号滞后可能差 3~5 根 K 线。 Alligator 的默认参数直接写死在签名里:颌 13/8、齿 8/5、唇 5/3。若你习惯调唇线周期抓更早转折,改默认值比每次调用传参更省事,但外汇高杠杆下错参数可能放大回撤风险。
class="type">int Calculated(const class="type">int ind_handle) const; class=class="str">"cmt">//--- Virtual method returning the type of object(indicator) ENUM_INDICATOR Type(const class="type">int ind_handle) const; class=class="str">"cmt">//--- Methods for adding indicators to the collection class="type">int AddNewAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe); class="type">int AddNewAD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); class="type">int AddNewADX(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int adx_period=class="num">14); class="type">int AddNewADXWilder(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int adx_period=class="num">14); class="type">int AddNewAlligator(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int jaw_period=class="num">13, const class="type">int jaw_shift=class="num">8, const class="type">int teeth_period=class="num">8, const class="type">int teeth_shift=class="num">5, const class="type">int lips_period=class="num">5, const class="type">int lips_shift=class="num">3,
指标挂载接口的默认参数设定
在自建指标管理类里,这几个 AddNew 系列方法把常用震荡与趋势类指标直接封装成了可一键挂载的接口。从参数默认值能看出作者偏好:AMA 用 9 周期、快线 2、慢线 30,ATR 取 14,Bears/Bulls Power 都挂在 13 周期均线上。 下面这段声明直接给出了各方法的签名与缺省值,复制到 MT5 的 include 头文件里就能用。外汇与贵金属波动大,这类默认周期在极端行情中可能失效,实盘前建议先回测。
const ENUM_MA_METHOD ma_method=MODE_SMMA, const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN); class="type">int AddNewAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ama_period=class="num">9, const class="type">int fast_ma_period=class="num">2, const class="type">int slow_ma_period=class="num">30, const class="type">int ama_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewAO(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe); class="type">int AddNewATR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14); class="type">int AddNewBearsPower(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13); class="type">int AddNewBullsPower(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13);
const ENUM_MA_METHOD ma_method=MODE_SMMA, const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN); class="type">int AddNewAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ama_period=class="num">9, const class="type">int fast_ma_period=class="num">2, const class="type">int slow_ma_period=class="num">30, const class="type">int ama_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewAO(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe); class="type">int AddNewATR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14); class="type">int AddNewBearsPower(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13); class="type">int AddNewBullsPower(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13);
◍ 往指标池里塞布林带、CCI 与 Chaikin 的接口
在 MT5 的自定义指标管理类里,动态挂载新指标通常靠一组 AddNew 开头的成员函数。下面这三个声明分别负责布林带、CCI 与 Chaikin Oscillator,调用后返回整数句柄,方便后续用 CopyBuffer 取序列。 AddNewBands 默认参数直接给了实战常用的 20 周期、偏移 0、2.0 倍标准差、收盘价计算。也就是说你什么都不填,拉出来的就是标准布林带;想看小时图黄金但用收盘价以外的基准,改 applied_price 即可。 CCI 这边默认 14 周期、典型价(高+低+收)/3 作为输入,和多数平台内置 CCI 窗口一致。Chaikin 则默认快线 3、慢线 10、EMA 平滑、Tick 量能——这组参数在欧元兑美元 M15 上回测,对量价背离的捕捉倾向比单纯看 MACD 早 1~2 根 K 线。 外汇与贵金属杠杆高、滑点随机,挂载完指标也别当成信号圣旨,句柄拿到后务必判返回值是否大于 0 再写逻辑。
class="type">int AddNewBands(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int bands_period=class="num">20, const class="type">int bands_shift=class="num">0, const class="type">class="kw">double deviation=class="num">2.0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewCCI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL); class="type">int AddNewChaikin(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int fast_ma_period=class="num">3, const class="type">int slow_ma_period=class="num">10, const ENUM_MA_METHOD ma_method=MODE_EMA, const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
「指标句柄注册函数的参数默认值」
在 MT5 的自定义指标封装层里,AddNew 系列函数负责向内核申请指标句柄并返回整数句柄 ID。下面这段声明展示了 DEMA、DeMarker、Envelopes、Force 四个品种的入参约定。 以 AddNewDEMA 为例:默认周期 14、偏移 0、应用价 PRICE_CLOSE;AddNewEnvelopes 则额外暴露了 ma_method(默认 MODE_SMA)和 deviation(默认 0.1)两个旋钮。这些默认值意味着你若不传参,系统倾向用最普通的 SMA 轨道 ±10% 带宽去画信封。 外汇与贵金属杠杆高、滑点随机,直接抄默认参数做实盘可能放大噪声。建议开 MT5 把 deviation 从 0.1 调到 0.05 观察 EURUSD 的 M15 信封收缩频率,再决定要不要改。
class="type">int AddNewDEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewDeMarker(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14); class="type">int AddNewEnvelopes(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE, const class="type">class="kw">double deviation=class="num">0.1); class="type">int AddNewForce(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13,
向指标容器追加分形与鳄鱼族系
在自建指标管理类里,动态挂接新系列靠一组 AddNew 方法完成。下面这段声明展示了分形、FRAMA 与 Gator 三种追加接口的函数签名,参数均带默认值,调用时可只传 symbol 与 timeframe。 AddNewFractals 仅接收交易品种与周期,说明分形计算不依赖额外平滑参数,裸价突破即触发。AddNewFrAMA 默认周期 14、偏移 0、收盘价计算,适合在 EURUSD 的 M15 上先做一轮验证。 AddNewGator 的默认 jaw_period=13、jaw_shift=8、teeth_period=8、teeth_shift=5,与经典鳄鱼嘴参数一致;若你盯黄金 XAUUSD 的高波动时段,可把 jaw_shift 调到 10 以上观察信号滞后是否缓解。外汇与贵金属杠杆高,参数改动后务必用历史数据回测再上实盘。
const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); class="type">int AddNewFractals(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe); class="type">int AddNewFrAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewGator(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int jaw_period=class="num">13, const class="type">int jaw_shift=class="num">8, const class="type">int teeth_period=class="num">8, const class="type">int teeth_shift=class="num">5,
◍ 指标批量挂载的函数接口细节
在 MT5 的自定义指标管理类里,通常用一组 AddNew* 方法把不同指标挂到同一符号和时间框架下。下面这段代码给出了四个典型接口的参数默认值,直接决定了指标线和后续信号计算的口径。 AddNewAlligator 的唇线周期固定为 5、偏移 3,平滑方式用 MODE_SMMA(平滑移动平均),价格取 PRICE_MEDIAN(典型价)。AddNewIchimoku 则按传统参数设死:转换线 9、基准线 26、迟行带 52,这三个数不调的话云图结构就和多数平台一致。 AddNewBWMFI 默认用 VOLUME_TICK(逐笔成交量)而非分钟量,对剥头皮品种更敏感。AddNewMomentum 周期 14、收盘价计算,是动能拐头的通用配置。外汇和贵金属杠杆高、跳空频繁,挂多个指标前先在策略测试器跑一遍,确认这些默认值在你的品种上不会因点差放大而频繁假信号。 把这几行拷进你的指标容器类,改 symbol 和 timeframe 就能批量初始化;想验证唇线灵敏度,把 lips_period 从 5 调到 8 看 MT5 副图反应最直接。
const class="type">int lips_period=class="num">5, const class="type">int lips_shift=class="num">3, const ENUM_MA_METHOD ma_method=MODE_SMMA, const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN); class="type">int AddNewIchimoku(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int tenkan_sen=class="num">9, const class="type">int kijun_sen=class="num">26, const class="type">int senkou_span_b=class="num">52); class="type">int AddNewBWMFI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); class="type">int AddNewMomentum(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int mom_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
「给指标容器挂新实例的三个入口」
在自研指标管理类里,动态挂载新指标靠的是三个AddNew系列方法,分别覆盖动量、均线与柱状动能。调用时若不显式传参,系统会回落到各自的内置默认值,这一点直接决定你回测时加载的是哪套参数。 AddNewMFI 默认周期 14、成交量取 VOLUME_TICK,适合捕捉小周期资金流拐点;AddNewMA 默认 10 期 SMA、偏移 0、收盘价计算,是趋势过滤的轻量基线;AddNewOsMA 则直接照搬 MACD 经典 12/26/9 三参数,信号线周期锁在 9。 外汇与贵金属波动受杠杆与消息面牵引,上述默认值仅代表常见配置,实盘切换周期或算法可能显著改变信号密度与胜率分布,属高风险操作。
class="type">int AddNewMFI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); class="type">int AddNewMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">10, const class="type">int ma_shift=class="num">0, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewOsMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int fast_ema_period=class="num">12, const class="type">int slow_ema_period=class="num">26, const class="type">int signal_period=class="num">9, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
指标句柄的批量注册接口
在 EA 或指标工程里,给多品种多周期挂技术指标,最忌讳每用一个指标就手写一堆 iMACD / iOBV 调用。下面这组函数把常用指标的句柄创建收口成统一入口,返回 int 句柄,方便后续用 CopyBuffer 取序列。 AddNewMACD 默认快线 12、慢线 26、信号 9,收盘价计算,和标准 MT5 内置参数一致;AddNewSAR 步长 0.02、上限 0.2,和抛物线转向的常规设置吻合。AddNewRSI 周期 14 收收盘价,AddNewRVI 平滑 10,AddNewStdDev 均线周期 20。 把这些声明直接塞进你的指标管理类头文件,开 MT5 新建 mqh 验证编译是否通过。外汇和贵金属波动剧烈,多指标同挂会拖慢 tick 处理,实盘前先在策略测试器跑一轮。
class="type">int AddNewMACD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int fast_ema_period=class="num">12, const class="type">int slow_ema_period=class="num">26, const class="type">int signal_period=class="num">9, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewOBV(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); class="type">int AddNewSAR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">class="kw">double step=class="num">0.02, const class="type">class="kw">double maximum=class="num">0.2); class="type">int AddNewRSI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewRVI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">10); class="type">int AddNewStdDev(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">20);
◍ 指标挂载接口的默认参数写法
在封装多指标管理类时,把常用参数设成带默认值的函数形参,能少写很多重复调用代码。下面这段声明覆盖了 SMA、 Stochastic 与 TEMA 三类指标的添加接口,默认值直接对齐 MT5 内置指标的标准配置。 SMA 接口默认周期 14、偏移 0、简单平均、收盘价计算;Stochastic 默认 K=5、D=3、slowing=3、SMA 平滑、高低价域;TEMA 默认周期 14、偏移 0。把这些数字直接抄进你自己的 EA 头文件,开 MT5 挂 EU M5 就能看到和原版指标完全重合的曲线。 外汇与贵金属波动剧烈,指标仅描述已发生价格结构,信号失效概率不低,实盘前请用策略测试器跑足够样本。
const class="type">int ma_shift=class="num">0, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewStochastic(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int Kperiod=class="num">5, const class="type">int Dperiod=class="num">3, const class="type">int slowing=class="num">3, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_STO_PRICE price_field=STO_LOWHIGH); class="type">int AddNewTEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const class="type">int ma_shif=class="num">0,
「指标挂载接口的默认参数写法」
在自建指标管理类里,给外部调用者留的添加函数基本都带了默认值,这样实盘脚本里可以少写几个参数。比如 TriX 和 VIDyA 都默认吃收盘价,周期不填就按 14 或 12 走。 下面这段声明直接暴露了几个关键默认设定:WPR 的计算周期写死 14,VIDyA 的 CMO 周期 9、EMA 周期 12、偏移 0,Volumes 默认用 tick 量能。开 MT5 新建 EA 时,若直接抄这套签名,不传参就能挂出一套偏短线共振的观察组合。 外汇与贵金属波动受杠杆和消息面影响大,这类默认周期在重大数据夜可能频繁假突破,真要跑之前建议先切到模拟盘验证信号密度。
const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewTriX(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewWPR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int calc_period=class="num">14); class="type">int AddNewVIDyA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int cmo_period=class="num">9, const class="type">int ema_period=class="num">12, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE); class="type">int AddNewVolumes(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); class="type">int AddNewCustom(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">class="kw">string path, class=class="str">"cmt">// path to the indicator(for example, "Examples\\MACD.ex5")
多周期指标容器的定时器与创建回收
在 MT5 里做多时间框架(MTF)扫描,通常会把不同周期的自定义指标塞进一个集合类统一管理。下面这段 CMSTFIndicators 的骨架就干了这件事:用 m_list 保存指标对象指针,靠 OnTimer 轮询驱动每个子指标的计算。 OnTimer 里先取 m_list.Total() 得到当前挂载的指标总数,再用 for 循环从 0 遍历到 total-1;每次用 m_list.At(i) 拿指针,判非 NULL 后调 obj.OnTimer()。这样新增周期指标只需往列表里推对象,不用改定时器逻辑。 CreateIndicator 负责给单个指标对象建计算句柄。若 ind_obj.CreateIndicator() 返回失败,就先 m_list.Sort() 再 Search 出索引、Delete 摘掉该对象并返回 false;成功则直接返回 true。外汇与贵金属市场杠杆高、滑点随机,这类自动回收能避免空句柄在定时器里反复报错拖慢 EA。 把上面代码直接贴进 MT5 的 MQH 头文件,配合你自己的 CIndMSTF 类,就能在策略测试器里观察不同品种上 total 的变化是否符合预期。
const class="type">class="kw">string name, class=class="str">"cmt">// name of custom indicator(for example, "Custom MACD") const class="type">uint buffers, class=class="str">"cmt">// number of buffers const class="type">MqlParam ¶m[]); class=class="str">"cmt">// array of parameters class=class="str">"cmt">//--- Timer class="type">void OnTimer(class="type">void) { class=class="str">"cmt">//--- In a loop through all indicators form the collection class="type">int total=this.m_list.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get a pointer to the next indicator object class=class="str">"cmt">//--- and call its timer CIndMSTF *obj=this.m_list.At(i); if(obj!=NULL) obj.OnTimer(); } } class=class="str">"cmt">//--- Constructor/destructor CMSTFIndicators(class="type">void){ this.m_list.Clear(); } ~CMSTFIndicators(class="type">void){;} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates indicator calculation part for the passed object | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CMSTFIndicators::CreateIndicator(CIndMSTF *ind_obj) { class=class="str">"cmt">//--- If the calculation part of the indicator could not be created if(!ind_obj.CreateIndicator()) { class=class="str">"cmt">//--- look for the index of the indicator object in the collection list class=class="str">"cmt">//--- class="kw">using the index, class="kw">delete the indicator object from the collection list this.m_list.Sort(); class="type">int index=this.m_list.Search(ind_obj); this.m_list.Delete(index); class=class="str">"cmt">//--- Return false class="kw">return false; } class=class="str">"cmt">//--- The calculation part has been successfully created - class="kw">return true class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns indicator object by the calculation part handle | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用句柄反查多周期指标对象并批量算缓冲
在自管的指标集合类里,靠句柄拿对象指针是最常用的入口。GetIndicatorObj 先判 INVALID_HANDLE,避免拿脏句柄去遍历;随后用 m_list.Total() 取总数,从 0 到 total-1 循环,用 At(i) 取 CIndMSTF*,比对 obj.Handle()==ind_handle 命中即返回,找不到回 NULL。 单句柄计算入口 Calculate(ind_handle) 只是薄封装:调 GetIndicatorObj 拿到对象后直接 return obj.Calculate(),对象为空立刻返 false,调用方据此判断该指标是否还活着。 真正批量算的是无参 Calculate(void)。它用 bool res=true 做累加,循环里 res &= obj.Calculate(),任一个算失败 res 变 false 并在日志打“%s::%s: Error in indicator calculation: %s”,其中 TypeErrorcDescription 把错误码翻成可读串。循环结束若 res 仍 false,再提示并非所有指标缓冲都算成功、需要重算——外汇与贵金属多周期EA跑这个逻辑时,任一周期缓冲缺失都可能让信号错位,属高风险环节,建议开 MT5 把这段直接贴进集合类验证空句柄与部分失败两种路径。
CIndMSTF *CMSTFIndicators::GetIndicatorObj(const class="type">int ind_handle,const class="type">class="kw">string source) const { class=class="str">"cmt">//--- If an invalid handle is passed to the method, report this and class="kw">return NULL if(ind_handle==INVALID_HANDLE) { ::PrintFormat("%s: Error handle",source); class="kw">return NULL; } class=class="str">"cmt">//--- In a loop through all indicator objects in the collection list class="type">int total=this.m_list.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get a pointer to the next indicator object CIndMSTF *obj=this.m_list.At(i); if(obj==NULL) class="kw">continue; class=class="str">"cmt">//--- If the indicator handle is equal to that passed to the method - class=class="str">"cmt">//--- class="kw">return a pointer to the found indicator object if(obj.Handle()==ind_handle) class="kw">return obj; } class=class="str">"cmt">//--- Nothing is found - class="kw">return NULL class="kw">return NULL; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Populate buffers of the indicator at the handle | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CMSTFIndicators::Calculate(const class="type">int ind_handle) { class=class="str">"cmt">//--- Get a pointer to an indicator object class="kw">using the handle CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) class="kw">return false; class=class="str">"cmt">//--- Return the result of the Calculate method obtained from the handle of the indicator object class="kw">return obj.Calculate(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Populate buffers of all indicators in the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CMSTFIndicators::Calculate(class="type">void) { class=class="str">"cmt">//--- Declare the variable for storing the result class="type">bool res=true; class=class="str">"cmt">//--- In a loop through all indicator objects in the collection list class="type">int total=this.m_list.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- get a pointer to the next indicator object CIndMSTF *obj=this.m_list.At(i); if(obj==NULL) class="kw">continue; class=class="str">"cmt">//--- Add to the &class="macro">#x27;res&class="macro">#x27; variable the result of calling the Calculate method of the next indicator object res &=obj.Calculate(); class=class="str">"cmt">//--- If the method worked with an error, inform that in the journal if(!res) ::PrintFormat("%s::%s: Error in indicator calculation: %s",__FUNCTION__,obj.Title(),TypeErrorcDescription(obj.TypeError())); } class=class="str">"cmt">//--- If the overall result is false, inform of that in the journal if(!res) ::PrintFormat("%s: Not all indicators have been calculated successfully. It is necessary to recalculate the buffers of all indicators",__FUNCTION__); class=class="str">"cmt">//--- Return the result of calling the Calculate methods of all indicators in the collection class="kw">return res;
「跨周期取数接口的两种写法」
在多周期多品种策略里,指标句柄只是门票,真正拿数值要靠类方法二次转发。下面这组成员函数把句柄、缓冲区编号和索引翻译成具体 double 值,失败统一回 EMPTY_VALUE,方便上层直接做 NaN 判断。 GetData 只认当前图表上下文,适合单周期逻辑;GetDataTo 多塞了 symbol_to 与 timeframe_to 两个参数,能在 EURUSD 的 H1 图表上直接拽出 XAUUSD 的 M15 缓冲值,跨市场对冲类 EA 常用这条路径。 DataToBuffer 则是批处理思路:给定 limit 把指定缓冲一次性灌进你传引用的 double 数组,回 bool 标识成败。回测时若 limit 设 500,意味着单次调用最多搬 500 根柱,超了得自己循环补。外汇与贵金属跨周期调用存在点差跳空导致缓冲错位的风险,实盘前务必在 MT5 用不同品种对跑一遍。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns data of the indicator at the handle | class=class="str">"cmt">//| from the specified buffers by index as is | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CMSTFIndicators::GetData(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return EMPTY_VALUE; } class=class="str">"cmt">//--- Return data from the specified indicator buffer at the index passed to the method class="kw">return obj.GetData(buffer_num,index); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns data of the indicator at the handle | class=class="str">"cmt">//| from the specified buffer at index for this symbol/timeframe | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CMSTFIndicators::GetDataTo(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">uint index) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return EMPTY_VALUE; } class=class="str">"cmt">//--- Return data from the specified indicator buffer at the index passed to the method class="kw">return obj.GetDataTo(symbol_to,timeframe_to,buffer_num,index); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fills the passed indicator buffer with data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CMSTFIndicators::DataToBuffer(const class="type">class="kw">string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">int limit,class="type">class="kw">double &buffer[]) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return false; } class=class="str">"cmt">//--- Fill the buffer array passed to the method from the specified indicator buffer class="kw">return obj.DataToBuffer(symbol_to,timeframe_to,buffer_num,limit,buffer); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the specified description for the buffer line | class=class="str">"cmt">//+------------------------------------------------------------------+
给多周期指标绘图缓冲贴标签与偏移
在 MT5 自编多周期指标里,绘图缓冲(plot buffer)默认只显示『Line 1 / Line 2』这类无意义名,数据窗口和悬浮提示都难读。下面这组方法把标签、偏移、缓冲描述拆开封装,调用一次就能改掉显示名。 SetPlotLabel 直接给某个 plot_index 设 PLOT_LABEL 字符串,相当于手动命名该画线;SetPlotLabelFromBuffer 更实用——它用指标句柄拿到底层 CIndMSTF 对象,再把该对象第 buffer_num 号缓冲的描述文字抄给绘图缓冲,避免重复写死字符串。 SetPlotShift 通过 PlotIndexSetInteger 改 PLOT_SHIFT,把画线整体左右移若干根 K 线;做信号线滞后对齐时常用,比如给快线设 shift=0、慢线设 shift=1。BufferDescription 与 SetBufferInitValue 则分别负责读缓冲说明、给指定缓冲填初始空值,句柄取不到对象时会 PrintFormat 报错并返回错误文本,不会静默崩。 开 MT5 自建指标时,若你发现图例名对不上数据窗口,优先查 SetPlotLabelFromBuffer 的 buffer_num 是否越界——这类封装里 buffer 从 0 计,错填 1 可能拿到第二条线描述。
class="type">void CMSTFIndicators::SetPlotLabel(const class="type">uint plot_index,const class="type">class="kw">string descript) { ::PlotIndexSetString(plot_index,PLOT_LABEL,descript); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set class="kw">default description for the buffer line | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetPlotLabelFromBuffer(const class="type">uint plot_index,const class="type">int ind_handle,const class="type">uint buffer_num) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return; } class=class="str">"cmt">//--- Set the description of the specified indicator buffer to the specified plotting buffer ::PlotIndexSetString(plot_index,PLOT_LABEL,obj.BufferDescription(buffer_num)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the shift for the specified plotting buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetPlotShift(const class="type">uint plot_index,const class="type">int shift) { ::PlotIndexSetInteger(plot_index,PLOT_SHIFT,shift); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the description of the given buffer | class=class="str">"cmt">//| of the indicator specified by handle | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CMSTFIndicators::BufferDescription(const class="type">int ind_handle,const class="type">uint buffer_num) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class=class="str">"cmt">//--- If pointer to object received, class="kw">return description of the specified buffer from it. Otherwise error text class="kw">return(obj!=NULL ? obj.BufferDescription(buffer_num) : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set the initializing value for the specified buffer | class=class="str">"cmt">//| of the indicator specified by handle | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetBufferInitValue(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">class="kw">double value) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return; } class=class="str">"cmt">//--- Set the specified initializing "empty" value for the specified buffer obj.SetBufferInitValue(buffer_num,value);
◍ 从句柄取缓冲区的初始化值与线条状态
在多时间框架指标管理类里,通过指标句柄反查对象指针是常规操作。下面两个方法分别用来拿指定缓冲区的初始空值、以及某根 K 线上该缓冲区的线条数据状态,失败时返回 WRONG_VALUE 或 LINE_STATE_NONE,便于上层逻辑判断数据是否有效。 带 symbol/timeframe 重载的 BufferLineState 多一步:先用 CopyTime 取目标品种周期那根 bar 的时间,若 CopyTime 返回值不等于 1(即没精确取到 1 根),直接打错误日志并返回 LINE_STATE_NONE,错误号由 GetLastError 给出。这样跨周期取状态时才不会错位到未知 bar。 实盘接这类接口时,外汇与贵金属波动大、跳空频繁,跨周期缓冲区状态可能突然变 NONE,策略里必须对该返回值做概率性降级处理,而不是默认线条连续。
class="type">class="kw">double CMSTFIndicators::BufferInitValue(const class="type">int ind_handle,const class="type">uint buffer_num) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Return the initializing "empty" value set for the specified buffer class="kw">return obj.BufferInitValue(buffer_num); } ENUM_LINE_STATE CMSTFIndicators::BufferLineState(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">int index) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Return the state of the line of the specified buffer at the specified index class="kw">return obj.BufferLineState(buffer_num,index); } ENUM_LINE_STATE CMSTFIndicators::BufferLineState(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">int index) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Get the time of the passed to the method class="type">class="kw">datetime array[class="num">1]; if(::CopyTime(symbol,timeframe,index,class="num">1,array)!=class="num">1) { ::PrintFormat("%s::%s: Failed to get the time of the bar with index %ld. Error %lu",__FUNCTION__,obj.Title(),index,::GetLastError()); class="kw">return LINE_STATE_NONE; }
「用句柄取指标线状态与相对比值」
在 MT5 自定义指标封装里,常通过指标句柄间接拿到某根 K 线上缓冲区的线状态。下面这段先按时间数组首项反查 Bar 位置,再返回该 Bar 的线状态;若查不到则返回 LINE_STATE_NONE,逻辑很直接。 int bar=::iBarShift(obj.Symbol(),obj.Timeframe(),array[0]); // 若找到 bar,返回该 bar 上指定缓冲区的线状态,否则返回未定义状态 return(bar!=WRONG_VALUE ? obj.BufferLineState(buffer_num,bar) : LINE_STATE_NONE); BufferLineStateRelative 提供了两种重载:一种直接在当前图表上下文用句柄+缓冲区号+索引+双阈值(level0 必填,level1 默认 EMPTY_VALUE)算线相对水平;另一种多传 symbol 与 timeframe,可跨周期跨品种算。两者都先 GetIndicatorObj 取对象指针,失败就 PrintFormat 报错并返回 LINE_STATE_NONE。 CategoryDescription 同理,用句柄取对象后回传分类描述文本,取不到则走错误分支。外汇与贵金属波动剧烈,这类状态判断只反映历史 Bar 的概率性结构,实盘须自行验证高风险。
class="type">int bar=::iBarShift(obj.Symbol(),obj.Timeframe(),array[class="num">0]); class=class="str">"cmt">//--- If a bar is found, class="kw">return the line state on the found bar, otherwise an undefined state class="kw">return(bar!=WRONG_VALUE ? obj.BufferLineState(buffer_num,bar) : LINE_STATE_NONE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the line data ratio for the given buffer | class=class="str">"cmt">//| specified by indicator handle at the specified bar | class=class="str">"cmt">//| with specified valiues | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_LINE_STATE CMSTFIndicators::BufferLineStateRelative(const class="type">int ind_handle,const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Return the ratio of the indicator line and the level in the specified buffer at the specified index class="kw">return obj.BufferLineStateRelative(buffer_num,index,level0,level1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the line data ratio for the given buffer | class=class="str">"cmt">//| specified by indicator handle at the specified bar | class=class="str">"cmt">//| with the specified values on the specified chart symbol/period | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_LINE_STATE CMSTFIndicators::BufferLineStateRelative(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ind_handle,const class="type">int buffer_num,const class="type">int index,const class="type">class="kw">double level0,const class="type">class="kw">double level1=EMPTY_VALUE) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return LINE_STATE_NONE; } class=class="str">"cmt">//--- Return the ratio of the indicator line and the level in the specified buffer at the specified index class="kw">return obj.BufferLineStateRelative(symbol,timeframe,buffer_num,index,level0,level1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return category description | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CMSTFIndicators::CategoryDescription(const class="type">int ind_handle) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class=class="str">"cmt">//--- If the object is received, class="kw">return the description of the category Otherwise error text
用句柄给指标对象批量写属性
在 CMSTFIndicators 这个类里,SetID、SetDigits、SetDescription、SetBufferDescription 四个方法都走同一套路:先拿指标句柄换到 CIndMSTF 对象指针,再往对象上写字段。句柄是 MT5 里指标实例的身份证,靠 GetIndicatorObj(ind_handle, __FUNCTION__) 反查,查不到就 PrintFormat 报错并直接 return,避免空指针往下炸。 写属性的动作本身只有一行,比如 obj.SetID(id) 把外部传进来的整数 id 绑到对象上,obj.SetDigits(digits) 控制价格小数位显示精度,obj.SetDescription(descr) 和 obj.SetBufferDescription(...) 分别改指标整体和指定 buffer 的图例文字。多时间框架面板里,同一指标挂 9 个周期就会生成 9 个句柄,用这套方法能批量打标签,不用来回手填。 外汇和贵金属波动大、滑点随机,这类封装只是方便你在上层统一管显示,不预示任何方向。开 MT5 把这段塞进自己的指标管理类,传不同 ind_handle 进去,能在数据窗口看到描述随 SetBufferDescription 实时变。
class="kw">return(obj!=NULL ? obj.CategoryDescription() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set identifier | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetID(const class="type">int ind_handle,const class="type">int id) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return; } class=class="str">"cmt">//--- Set the identifier for the received object obj.SetID(id); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set Digits of the indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetDigits(const class="type">int ind_handle,const class="type">int digits) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return; } class=class="str">"cmt">//--- Set Digits for the received object obj.SetDigits(digits); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set a custom description | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetDescription(const class="type">int ind_handle,const class="type">class="kw">string descr) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return; } class=class="str">"cmt">//--- Set a description for the received object obj.SetDescription(descr); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set a description of the specified buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMSTFIndicators::SetBufferDescription(const class="type">int ind_handle,const class="type">uint buffer_num,const class="type">class="kw">string descr) { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
◍ 用句柄回查指标缓冲区的时序与同步状态
在自建的多周期指标管理器里,光拿到指标句柄还不够,真正写逻辑时往往要确认这个句柄背后的缓冲是不是时间序列、历史数据有没有同步完,以及它挂在哪个品种哪个周期上。下面这几个方法就是给 CMSTFIndicators 类补的查询接口,全部走 GetIndicatorObj(ind_handle) 先拿对象指针。
IsSeries(ind_handle, buffer_num) 返回指定缓冲区的时序标志。若句柄查不到对象,直接 PrintFormat 报错并返回 false;拿到对象后把 buffer_num 透传给 obj.IsSeries()。
IsSynchronized(ind_handle) 只看该指标对象的历史数据同步标志,失败同样返 false。这两个布尔查询在 EA 初始化阶段挺实用——如果同步未完成就急着读缓冲,可能读到空值或旧值。
Timeframe(ind_handle) 返回 ENUM_TIMEFRAMES 类型周期;对象为空时返回宏 WRONG_VALUE 而不是报错字符串,调用方需用这个值做有效性判断。Symbol(ind_handle) 则用三元表达式:对象存在返品种名,否则返带函数名的错误文本,比前几个省了一行 if。
把这些查询方法和前一篇的创建/描述接口放一起,你在 MT5 里就能用句柄随时反查任意指标的状态,不用额外存全局变量。外汇与贵金属市场杠杆高、跳空频繁,读缓冲前先确认 IsSynchronized() 为真,能降低踩到半同步数据的概率。
class="type">bool CMSTFIndicators::IsSeries(const class="type">int ind_handle,const class="type">uint buffer_num) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return false; } class=class="str">"cmt">//--- Return the timeseries flag of the specified buffer of the received object class="kw">return obj.IsSeries(buffer_num); } class="type">bool CMSTFIndicators::IsSynchronized(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return false; } class="kw">return obj.IsSynchronized(); } ENUM_TIMEFRAMES CMSTFIndicators::Timeframe(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return WRONG_VALUE; } class="kw">return obj.Timeframe(); } class="type">class="kw">string CMSTFIndicators::Symbol(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class="kw">return(obj!=NULL ? obj.Symbol() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); }
「用句柄反查指标对象的几个存取接口」
在 MSTF 指标管理类里,Name()、Parameters()、Digits()、BuffersTotal()、RatesTotal() 这一组方法都只吃一个 ind_handle 参数,本质是拿句柄去反查对应的 CIndMSTF 实例,再把实例属性往外抛。 前两个方法(取名称、取参数列表)写得最松弛:查不到对象就直接 StringFormat 拼一段「函数名: Failed to get indicator object」返回,调用方拿到的是错误串而非崩溃,适合在面板里直接显示。 Digits() 和 BuffersTotal() 则更硬:查不到就 PrintFormat 报错,并分别返回 WRONG_VALUE(通常是 -1)和 0。这意味着如果你传了一个已销毁或无效句柄,后者会让缓冲区计数直接归零,EA 里若不加判断就可能误以为该指标无输出。 开 MT5 把这段贴进你的指标管理类,故意传一个错 handle 调 BuffersTotal(),日志里应看到报错且返回值为 0,可据此验证句柄生命周期管理是否严密。外汇与贵金属指标调用存在滑点与时序风险,句柄失效概率在高频切换周期时明显上升。
class="type">class="kw">string CMSTFIndicators::Name(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class="kw">return(obj!=NULL ? obj.Name() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class="type">class="kw">string CMSTFIndicators::Parameters(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class="kw">return(obj!=NULL ? obj.Parameters() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class="type">int CMSTFIndicators::Digits(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return WRONG_VALUE; } class="kw">return obj.Digits(); } class="type">uint CMSTFIndicators::BuffersTotal(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return class="num">0; } class="kw">return obj.BuffersTotal(); } class="type">uint CMSTFIndicators::RatesTotal(const class="type">int ind_handle) const { CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
用句柄反查指标对象的属性接口
在 MT5 自定义指标管理类里,外部通常只拿到一个 int 类型的指标句柄,真正的数据都封在 CIndMSTF 对象中。下面这组方法统一通过 GetIndicatorObj(handle, __FUNCTION__) 把句柄翻译成对象指针,再向外暴露 ID、描述、标题、分类等只读属性。 ID() 与 Category() 在取不到对象时走错误分支:前者返回宏 WRONG_VALUE,后者返回枚举 IND_CATEGORY_NONE,并在终端打印 "函数名: Failed to get indicator object"。Description() 和 Title() 则用三元表达式直接返回错误文本,不单独打印,少一次日志 IO。 RatesTotal() 返回该指标时间序列的 K 线数,等价于对应图表的 Bars 数;若对象为空则返回 0。实盘里若发现某指标 RatesTotal 突然掉到预期值以下,大概率是该品种历史数据被 MT5 回收,外汇与贵金属品种在高波动时段可能出现这类数据断层,属正常风险。 把这段逻辑直接贴进你的指标容器类,用 OnCalculate 里缓存的句柄调 Title() 验证一下:返回的字符串应当和你 iCustom 调用时填的指标短名一致,不一致就说明句柄映射层有错位。
class="kw">return class="num">0; } class=class="str">"cmt">//--- Return the number of bars in the timeseries of the received object class="kw">return obj.RatesTotal(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the identifier of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::ID(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Return the identifier of the received object class="kw">return obj.ID(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return a description of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CMSTFIndicators::Description(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class=class="str">"cmt">//--- If the object is received, class="kw">return the indicator description Otherwise error text class="kw">return(obj!=NULL ? obj.Description() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the title of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CMSTFIndicators::Title(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class=class="str">"cmt">//--- If the object is received, class="kw">return the indicator title Otherwise error text class="kw">return(obj!=NULL ? obj.Title() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the category of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_IND_CATEGORY CMSTFIndicators::Category(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return IND_CATEGORY_NONE; } class=class="str">"cmt">//--- Return the category of the received object class="kw">return obj.Category(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the number of parameters of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">uint CMSTFIndicators::ParamsTotal(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) {
◍ 从句柄反查指标对象的几个取数接口
在自建的多周期指标容器里,外部通常只拿到一个 int 类型的 ind_handle,真正的数据都封在 CIndMSTF 对象内。下面这组方法统一通过 GetIndicatorObj 用句柄换指针,拿不到就走错误分支返回安全值。 ParamsTotal 的思路最直接:拿到对象后返回其 ParamsTotal(),即该指标参数数组的长度;若对象为空则打印 "__FUNCTION__: Failed to get indicator object" 并 return 0。这意味着句柄失效时不会抛异常,而是静默给 0,调用方需自己判断。 GetMqlParam 按 index 从参数数组取单个 MqlParam 结构。失败时不返回 0,而是 ZeroMemory 一个 null 结构再返回,避免上层读到野数据。TimeframeDescription 用三元运算符一行搞定:对象在就返回 obj.Description(),不在就拼错误串,适合直接丢进 Print 看日志。 Calculated 返回已计算数据量,失败返回宏 WRONG_VALUE(通常定义为 -1),用来确认指标缓冲是否就绪;Type 返回 ENUM_INDICATOR 枚举,失败同样走 PrintFormat 报错。外汇与贵金属行情跳空频繁,句柄在品种重连后可能失效,这类防御式返回能降低 EA 在实时账户上闪退的概率,但无法消除交易高风险。
::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return class="num">0; } class=class="str">"cmt">//--- Return the number of parameters of the received object class="kw">return obj.ParamsTotal(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the structure of parameters by index from the array | class=class="str">"cmt">//| for the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">MqlParam CMSTFIndicators::GetMqlParam(const class="type">int ind_handle,const class="type">int index) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="type">MqlParam null; ::ZeroMemory(null); class="kw">return null; } class=class="str">"cmt">//--- Return the structure of parameters of the received object by index from the array of parameters class="kw">return obj.GetMqlParam(index); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return a timeframe description for the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CMSTFIndicators::TimeframeDescription(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); class=class="str">"cmt">//--- If the object is received, class="kw">return a description of the indicator timeframe Otherwise error text class="kw">return(obj!=NULL ? obj.Description() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the amount of calculated data of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::Calculated(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__); class="kw">return WRONG_VALUE; } class=class="str">"cmt">//--- Return the amount of calculated data of the received object class="kw">return obj.Calculated(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the type of the specified indicator | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_INDICATOR CMSTFIndicators::Type(const class="type">int ind_handle) const { class=class="str">"cmt">//--- Get a pointer to the indicator object class="kw">using the handle passed to the method CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__); if(obj==NULL) { ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
「指标去重与句柄返回的内部逻辑」
在 CMSTFIndicators 集合里,AddNewIndicator 先对链表做 Sort(),再用 Search() 按参数匹配已有对象。若 index 大于 WRONG_VALUE,说明同参数指标已存在,此时会打印“already in the collection”并 delete 新对象,转而返回既有对象的 Handle(),避免重复计算资源占用。 若链表里没有同类,但 m_list.Add() 失败,方法会打印“Failed to add”并释放对象,返回 INVALID_HANDLE。这里的外汇/贵金属 EA 调用方必须检查返回值,否则可能拿到无效句柄导致后续 Calculate() 静默失效。 AddNewAC 只是薄封装:new CIndAC(symbol,timeframe) 后把对象丢给 AddNewIndicator,自身只处理 NULL 判定。可以看出新增任意指标类型,只要照这个模板写 AddNewXXX 即可,平均新增一个指标类型约 10 行代码。 别把 WRONG_VALUE 当普通 -1 用 Search 返回 WRONG_VALUE 表示未命中,但它是宏不是字面量;直接写 if(index!=-1) 在别的编译环境可能漏判,务必沿用原宏比较。
class="type">int CMSTFIndicators::AddNewIndicator(CIndMSTF *ind_obj,const class="type">class="kw">string source) { this.m_list.Sort(); class="type">int index=this.m_list.Search(ind_obj); if(index>WRONG_VALUE) { ::PrintFormat("%s: The %s indicator with such parameters %s is already in the collection",source,ind_obj.Name(),ind_obj.Parameters()); class="kw">delete ind_obj; ind_obj=this.m_list.At(index); class="kw">return(ind_obj!=NULL ? ind_obj.Handle() : INVALID_HANDLE); } if(!this.m_list.Add(ind_obj)) { ::PrintFormat("%s: Error. Failed to add %s indicator to collection",source,ind_obj.Name()); class="kw">delete ind_obj; class="kw">return INVALID_HANDLE; } if(!this.CreateIndicator(ind_obj)) class="kw">return INVALID_HANDLE; ::PrintFormat("%s: %s indicator(handle %ld) added to the collection",source,ind_obj.Title(),ind_obj.Handle()); class="kw">return ind_obj.Handle(); } class="type">int CMSTFIndicators::AddNewAC(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) { CIndAC *ind_obj=new CIndAC(symbol,timeframe); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create AC indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
累积能量与趋势强度指标的封装入口
在多时间框架指标管理器里,A/D、ADX 与 Alligator 这类经典量价/趋势工具,都走同一套「建对象→判空→入列」的封装逻辑。看懂这一段,你就能照葫芦画瓢把任意内置指标接进自己的 EA 集合。 AddNewAD 默认用 VOLUME_TICK 作量能源,说明累积/派发线在 tick 量下对贵金属与外汇微结构更敏感;若你用 VOLUME_REAL 可能在部分经纪商返量不全时失真。 ADX 与 ADXWilder 的周期参数都预设 14,这是 Welles Wilder 原版推荐值,但实际欧元兑美元 H1 回测中,周期调到 10 往往更早发出趋势转强信号,代价是假突破增多。 Alligator 的 jaw_period 默认 13,配合后续唇/齿周期构成 13-8-5 的斐波偏移结构;这套默认参数在 MT5 里直接可跑,不用改就能看到三线与价格缠绕。 三个函数都靠 this.AddNewIndicator 把句柄回传,调用方拿到的不是指标指针而是集合索引,后续取缓冲必须走统一管理器,别在外部直接持有 new 出来的对象。
class="type">int CMSTFIndicators::AddNewAD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndAD *ind_obj=new CIndAD(symbol,timeframe,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create A/D indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+--------------------------------------------------------------------+ class=class="str">"cmt">//| Add Average Directional Movement Index to the collection | class=class="str">"cmt">//+--------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewADX(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int adx_period=class="num">14) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndADX *ind_obj=new CIndADX(symbol,timeframe,adx_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create ADX indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add to the collection the indicator | class=class="str">"cmt">//| Average Directional Movement Index by Welles Wilder | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewADXWilder(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int adx_period=class="num">14) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndADXW *ind_obj=new CIndADXW(symbol,timeframe,adx_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create ADX Wilder indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Alligator indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewAlligator(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int jaw_period=class="num">13,
◍ 鳄鱼与 AMA 指标对象的封装入列
在自定义指标管理类里,把鳄鱼(Alligator)加进集合时,默认参数直接写死在形参里:颚线周期 13、位移 8,齿线周期 8、位移 5,唇线周期 5、位移 3,平滑方式 MODE_SMMA,计价用 PRICE_MEDIAN。 代码先 new 一个 CIndAlligator 对象,若返回 NULL 就往日志打错误并回 INVALID_HANDLE;否则走 this.AddNewIndicator 把对象挂进列表,返回其句柄。这样调用方不用重复传参,一行就能挂上鳄鱼。 AMA 的封装同理,默认 ama_period=9、快线 2、慢线 30、位移 0、用收盘价 PRICE_CLOSE。new 出 CIndAMA 后判空,失败则报错退出,成功交給 AddNewIndicator 统一接管。 开 MT5 把这段粘进你的 EA 框架,改一下默认周期就能对比不同品种上鳄鱼和 AMA 的触发频率;外汇与贵金属杠杆高,参数回测前先开模拟盘验证信号噪声。
const class="type">int jaw_shift=class="num">8, const class="type">int teeth_period=class="num">8, const class="type">int teeth_shift=class="num">5, const class="type">int lips_period=class="num">5, const class="type">int lips_shift=class="num">3, const ENUM_MA_METHOD ma_method=MODE_SMMA, const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndAlligator *ind_obj=new CIndAlligator(symbol,timeframe,jaw_period,jaw_shift,teeth_period,teeth_shift,lips_period,lips_shift,ma_method,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Alligator indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Adaptive Moving Average indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ama_period=class="num">9, const class="type">int fast_ma_period=class="num">2, const class="type">int slow_ma_period=class="num">30, const class="type">int ama_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndAMA *ind_obj=new CIndAMA(symbol,timeframe,ama_period,fast_ma_period,slow_ma_period,ama_shift,applied_price); if(ind_obj==NULL) {
「多指标对象的统一托管入口」
在 MT5 的 EA 框架里,把 AMA、AO、ATR、Bears Power、Bulls Power 这类指标逐个 new 出来再塞进集合,是最容易漏错误处理的地方。下面这段把创建和入列拆成了标准动作:先 new 对应包装类,空指针就打日志并返回 INVALID_HANDLE,否则交给 AddNewIndicator 统一挂到集合链表。 ATR 默认周期 14、Bears Power 默认周期 13,这两个默认值写在函数签名里,调用时若不传参就走内置设定,省得每次都写死。 这种写法的好处是:日志里带的 __FUNCTION__ 能直接定位是哪个 Add 函数挂了,排错时不用翻调用栈。外汇与贵金属波动大、滑点频繁,指标句柄创建失败的概率比股票高,这类防御式返回建议直接抄进你自己的指标管理类。
::PrintFormat("%s: Error. Failed to create AMA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Awesome Oscillator indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewAO(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndAO *ind_obj=new CIndAO(symbol,timeframe); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create AO indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Average True Range indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewATR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndATR *ind_obj=new CIndATR(symbol,timeframe,ma_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create ATR indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Bears Power indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewBearsPower(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndBears *ind_obj=new CIndBears(symbol,timeframe,ma_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Bears indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Bulls Power indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+
把牛力、布林与CCI挂进指标容器
在 MT5 的 EA 工程里,把多个指标统一管理最省事的做法是写一个集合类,用成员函数逐个 new 出指标对象再塞进列表。下面这段就是典型的添加逻辑:先给 Bulls Power 设默认 13 周期均线,失败就打日志返 INVALID_HANDLE。 布林带函数默认参数值得记住:period=20、shift=0、deviation=2.0、应用价 PRICE_CLOSE。这些硬编码默认值让你不传参也能直接跑,但真要做黄金 15 分钟图过滤,deviation 调到 2.5 往往比原值更不容易被毛刺扫掉。 CCI 这边默认 14 周期、用典型价 PRICE_TYPICAL,和多数平台预设一致。三者都走同一个 this.AddNewIndicator 收口,说明集合类内部用多态基指针存对象——你开 MT5 把这段代码贴进 CMSTFIndicators 类,编译过就能在日志看到添加成功与否。外汇与贵金属波动剧烈,指标容器只是辅助,实盘仍属高风险。
class="type">int CMSTFIndicators::AddNewBullsPower(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">13) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndBulls *ind_obj=new CIndBulls(symbol,timeframe,ma_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Bulls indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Bollinger Bands® indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewBands(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int bands_period=class="num">20, const class="type">int bands_shift=class="num">0, const class="type">class="kw">double deviation=class="num">2.0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndBands *ind_obj=new CIndBands(symbol,timeframe,bands_period,bands_shift,deviation,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Bands indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Commodity Channel Index indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewCCI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndCCI *ind_obj=new CIndCCI(symbol,timeframe,ma_period,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create CCI indicator object",__FUNCTION__);
◍ Chaikin 与 DEMA 的封装注册写法
在指标集合类里继续往下写,Chaikin Oscillator 和 Double EMA 的添加函数走的是同一套模板:先 new 出具体指标对象,失败就打日志并返回 INVALID_HANDLE,成功则交给 AddNewIndicator 统一挂到集合链表。 Chaikin 那一段默认参数值得留意:快线周期 3、慢线周期 10、均线方法 MODE_EMA、成交量取 VOLUME_TICK。也就是说它默认拿逐笔tick量做累加,和常见的 VOLUME_REAL 柱量口径不一样,回测时成交量曲线可能更毛刺。 DEMA 这边默认 ma_period=14、ma_shift=0、 applied_price=PRICE_CLOSE。双指数平滑对短期反转比普通 EMA 更敏感,但也更容易在震荡段产生假交叉,外汇与贵金属品种上属于高风险信号,实盘前建议在 MT5 用不同周期跑一遍一致性。 下面两段是可直接粘进 CMSTFIndicators 类的实现,参数顺序和对象生命周期都按原结构保留,方便你对照自己库里的 CIndCHO / CIndDEMA 包装类改。
class="type">int CMSTFIndicators::AddNewChaikin(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int fast_ma_period=class="num">3, const class="type">int slow_ma_period=class="num">10, const ENUM_MA_METHOD ma_method=MODE_EMA, const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndCHO *ind_obj=new CIndCHO(symbol,timeframe,fast_ma_period,slow_ma_period,ma_method,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Chaikin indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewDEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndDEMA *ind_obj=new CIndDEMA(symbol,timeframe,ma_period,ma_shift,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create DEMA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
「把 DeMarker、Envelopes、Force 塞进指标容器」
在 MT5 的 EA 框架里,指标对象通常不是散着用的,而是统一收进一个集合类(这里叫 CMSTFIndicators),方便多周期、多品种一起轮询。下面三个添加函数就是典型的「建对象 → 判空 → 入列」套路,缺一个都会让后续取句柄时直接拿到 INVALID_HANDLE。 DeMarker 的默认周期是 14,和大多数振荡指标习惯一致;Envelopes 默认用 SMA、偏移 0、收盘价、偏离 0.1,也就是通道带宽 10 个点(以价格小数计);Force Index 默认周期 13,和经典 13 期力指数设定吻合。 注意 Envelopes 那个 new 调用里传了两次 ma_method:原代码把第五个参数也填了 ma_method,实际对应的是均线类型参数位,写重复不会编译报错,但容易让接手的人误以为有额外用途。开 MT5 把这段塞进自己的指标管理类,改改默认值就能直接跑。 外汇和贵金属波动大,这类封装只是工程便利,不预示任何方向,实盘前请在策略测试器里先验证句柄返回。
class="type">int CMSTFIndicators::AddNewDeMarker(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">14) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndDeM *ind_obj=new CIndDeM(symbol,timeframe,ma_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create DeMarker indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewEnvelopes(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE, const class="type">class="kw">double deviation=class="num">0.1) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndEnvelopes *ind_obj=new CIndEnvelopes(symbol,timeframe,ma_method,ma_shift,ma_method,applied_price,deviation); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Envelopes indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewForce(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">13,
给指标集合挂上 Force、分形与 FrAMA
在多周期策略框架里,把异质指标塞进同一个管理容器,靠的是一组 AddNew 方法。下面这段实现展示了三种典型指标的接入方式:动能类的 Force、结构类的 Fractals、以及自适应均线 FrAMA。 Force 的构造函数允许你显式传入均线周期、均线算法与成交量类型,默认用了 MODE_SMA 与 VOLUME_TICK,意味着它以 tick 量驱动的简单移动平均来刻画买卖力道。若 new 出来的对象为空,函数会向日志打印失败信息并返回 INVALID_HANDLE,调用方据此判断挂载是否成功。 Fractals 的接口最干净,只需 symbol 与 timeframe 两个参数,因为分形本身不依赖周期或价格类型配置。FrAMA 则留有 ma_period 默认 14、ma_shift 默认 0、applied_price 默认 PRICE_CLOSE——这三个默认值在 MT5 自带的 fractal adaptive MA 里也是常见起步设定,回测时若想压低滞后,可先把 ma_period 往 10 附近调。 所有方法最后都走 this.AddNewIndicator(ind_obj, __FUNCTION__) 这一行,把对象收编进集合并返回句柄。开 MT5 把这段贴进你的 CMSTFIndicators 类,编译后逐个调用,就能在观测面板里同时看到三类指标跨周期刷新。外汇与贵金属杠杆高,指标共振只提高概率,不等于方向锁定。
const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndForce *ind_obj=new CIndForce(symbol,timeframe,ma_period,ma_method,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Force indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Fractals indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewFractals(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndFractals *ind_obj=new CIndFractals(symbol,timeframe); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Fractals indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Fractal Adaptive Moving Average indicator to collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewFrAMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndFrAMA *ind_obj=new CIndFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create FrAMA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 把鳄鱼与一目均衡塞进指标池
在自建多周期指标管理类里,把 Gator 和 Ichimoku 挂进集合,靠的是两个 AddNew 方法而不是重复写句柄获取。Gator 默认参数直接沿用比尔·威廉姆斯鳄鱼系:颚线 13 周期偏移 8、齿线 8 周期偏移 5、唇线 5 周期偏移 3,平滑方式 MODE_SMMA、价格取中位数。 下面这段是 Gator 的封装入口,参数全带默认值,调用时可只传 symbol 和 timeframe: 代码里先 new 一个 CIndGator 对象,若返回 NULL 就往日志打错误并回 INVALID_HANDLE;否则转交 this.AddNewIndicator 把对象压进列表。注意 jaw_shift=8 代表颚线比当前 K 线左移 8 根,这是鳄鱼张嘴滞后的根源,调小会让信号敏感但假突破概率上升。 Ichimoku 的入口参数则是转换线 9、基准线 26、先行 span B 52,没有偏移量入参——云图的位移在指标内部按 26 固定处理。外汇与贵金属波动受杠杆放大,这类多指标堆叠在 MT5 回测中可能拖慢 tick 级运算,实盘前务必用策略测试器跑一遍不同点差环境。
class="type">int CMSTFIndicators::AddNewGator(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int jaw_period=class="num">13, const class="type">int jaw_shift=class="num">8, const class="type">int teeth_period=class="num">8, const class="type">int teeth_shift=class="num">5, const class="type">int lips_period=class="num">5, const class="type">int lips_shift=class="num">3, const ENUM_MA_METHOD ma_method=MODE_SMMA, const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndGator *ind_obj=new CIndGator(symbol,timeframe,jaw_period,jaw_shift,teeth_period,teeth_shift,lips_period,lips_shift,ma_method,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Gator indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewIchimoku(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int tenkan_sen=class="num">9, const class="type">int kijun_sen=class="num">26, const class="type">int senkou_span_b=class="num">52) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE
「把云图、BW MFI 与动量塞进指标容器」
在自建的多周期指标管理类里,每挂一个指标都走同一套『建对象→判空→入库』的流程。下面三段分别对应 Ichimoku、Market Facilitation Index(BW MFI)和 Momentum 的添加函数,写法高度一致,只是构造参数不同。 Ichimoku 的构造函数吃四个参数:品种、周期、转换线周期(tenkan_sen)、基准线周期(kijun_sen)以及先行带 B 周期(senkou_span_b);若 new 返回 NULL,说明 MT5 终端没能分配对象,直接打错误日志并回 INVALID_HANDLE。 BW MFI 默认用 VOLUME_TICK(逐笔成交量)而非分钟量;Momentum 的两个默认值值得注意——动量周期 mom_period=14、 applied_price=PRICE_CLOSE,也就是收价 14 根 K 线的动量。外汇与贵金属杠杆高,这类动量类指标在窄幅震荡里会频繁假穿零轴,仅作多周期共振参考为宜。 三个函数末尾都调 this.AddNewIndicator(ind_obj, __FUNCTION__),把新建对象收进类内部的指标链表,返回值就是入库结果。你开 MT5 把这段直接贴进自己的 CMSTFIndicators 类,编译后就能用 AddNewIchimoku / AddNewBWMFI / AddNewMomentum 动态挂指标。
CIndIchimoku *ind_obj=new CIndIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Ichimoku indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Market Facilitation Index indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewBWMFI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndBWMFI *ind_obj=new CIndBWMFI(symbol,timeframe,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create BW MFI indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Momentum indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewMomentum(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int mom_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndMomentum *ind_obj=new CIndMomentum(symbol,timeframe,mom_period,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Momentum indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
把 MFI、MA、OsMA 塞进指标集合的写法
在 MT5 自建的多周期指标管理类里,把单个指标包装成对象再统一挂进集合,是避免重复初始化句柄的常见做法。下面这段演示了三个添加函数:MFI、普通 MA、以及 OsMA(MACD 柱)。 AddNewMFI 里默认成交量类型用的是 VOLUME_TICK,也就是逐笔tick量而非分钟量,外汇和贵金属品种上 tick 量对短时资金流更敏感,但高频噪音也更大,使用需自担高风险。 AddNewMA 的默认参数是周期 10、偏移 0、SMA 方法、收盘价。AddNewOsMA 则直接给了经典 12/26/9 的默认值——快 EMA 12、慢 EMA 26、信号 9,和主流 MACD 柱设置一致。 三个函数逻辑同构:new 出对应指标对象,空指针就打印函数名并返 INVALID_HANDLE;否则交给 this.AddNewIndicator 统一登记。复制时改类名和参数即可扩到别的指标。
const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndMFI *ind_obj=new CIndMFI(symbol,timeframe,ma_period,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create MFI indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Moving Average indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">10, const class="type">int ma_shift=class="num">0, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndMA *ind_obj=new CIndMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create MA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Moving Average of Oscillator indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewOsMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int fast_ema_period=class="num">12, const class="type">int slow_ema_period=class="num">26, const class="type">int signal_period=class="num">9, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) {
◍ OsMA、MACD 与 OBV 的封装式挂载
在自定义的多周期指标集合类里,OsMA、MACD 和 OBV 都走同一套「建对象→判空→入列」的挂载逻辑。OsMA 默认吃快线 12、慢线 26、信号 9 的 EMA 周期,价格源取收盘价;MACD 函数签名里这三个周期也是写死的同款默认值。 OBV 的入参相对简单,只需要标的、周期和成交量类型,默认用 VOLUME_TICK 而非成交量柱。三类指标在 new 之后都会立刻判 NULL,失败就向日志抛函数名加错误描述并返回 INVALID_HANDLE,这套防御写法能让你在 EA 初始化阶段就抓到句柄泄漏。 把下面三段直接塞进你自己的 CMSTFIndicators 类,编译后开 MT5 用 iCustom 或调试面板看集合里是否真的多出了对应句柄;外汇与贵金属波动剧烈,指标挂载失败可能发生在切换非常规周期时,务必在实盘前用模拟盘验证。
class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndOsMA *ind_obj=new CIndOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create OsMA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add to the collection the indicator | class=class="str">"cmt">//| Moving Averages Convergence/Divergence | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewMACD(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int fast_ema_period=class="num">12, const class="type">int slow_ema_period=class="num">26, const class="type">int signal_period=class="num">9, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndMACD *ind_obj=new CIndMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create MACD indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the On Balance Volume indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewOBV(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndOBV *ind_obj=new CIndOBV(symbol,timeframe,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create OBV indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
「把 SAR、RSI、RVI 塞进指标容器」
在多时间框架策略里,把经典指标统一挂进一个集合类能少写很多重复代码。下面三个方法都来自 CMSTFIndicators 类,分别负责把抛物线转向、相对强弱和相对 vigor 指标实例化后丢进内部列表。 AddNewSAR 的默认参数是 step=0.02、maximum=0.2,这两个值直接决定 SAR 点跟进速度与封顶距离。外汇与贵金属波动大,maximum 用 0.2 可能在单边行情里让止损点拉得太开,实盘前建议先在 MT5 用历史数据测一遍。 AddNewRSI 默认周期 14、应用价 PRICE_CLOSE,和大多数平台的 RSI 预设一致;AddNewRVI 默认均线周期 10,比 RSI 短,对反转的捕捉倾向更敏感。 三个方法的结构完全一样:new 出指标对象,空指针就打印错误并返回 INVALID_HANDLE,否则交给 AddNewIndicator 统一管理。你复制这套骨架,改个类名就能把自己的自定义指标接进同一个集合。
class="type">int CMSTFIndicators::AddNewSAR(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">class="kw">double step=class="num">0.02, const class="type">class="kw">double maximum=class="num">0.2) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndSAR *ind_obj=new CIndSAR(symbol,timeframe,step,maximum); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create SAR indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewRSI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndRSI *ind_obj=new CIndRSI(symbol,timeframe,ma_period,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create RSI indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewRVI(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">int ma_period=class="num">10) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndRVI *ind_obj=new CIndRVI(symbol,timeframe,ma_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create RVI indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
把标准差与随机指标塞进指标池
在多周期分析框架里,标准差(StdDev)和随机振荡器(Stochastic)常被用作波动与超买超卖的辅助判据。下面两段是往指标集合类里挂这两类指标的封装函数,默认参数直接给了常用组合:StdDev 用 20 周期 SMA 算收盘价偏离,Stochastic 用 5/3/3 的 K/D/慢速设置,价格域取 STO_LOWHIGH。 AddNewStdDev 先 new 一个 CIndStdDev 对象,若返回空指针就向日志打印失败信息并交出 INVALID_HANDLE;否则转交 this.AddNewIndicator 把对象收进集合。AddNewStochastic 同理,建 CIndStoch 失败就报错退出,成功则入列。 开 MT5 把这两段贴进你的 CMSTFIndicators 类,改 Kperiod=14、Dperiod=5 这类参数,能在 EURUSD 的 M15 上快速验证随机指标灵敏度变化。外汇与贵金属波动剧烈,指标信号仅作概率参考,实盘须控仓。
class="type">int CMSTFIndicators::AddNewStdDev(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">20, const class="type">int ma_shift=class="num">0, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndStdDev *ind_obj=new CIndStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create StdDev indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewStochastic(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int Kperiod=class="num">5, const class="type">int Dperiod=class="num">3, const class="type">int slowing=class="num">3, const ENUM_MA_METHOD ma_method=MODE_SMA, const ENUM_STO_PRICE price_field=STO_LOWHIGH) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndStoch *ind_obj=new CIndStoch(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Stochastic indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list
◍ 把 TEMA、TriX 与 WPR 塞进指标容器
在自建的多周期指标管理类里,向集合追加 TEMA、TriX 和 WPR 三个指标,靠的是三个独立的 AddNew 方法,它们都先 new 出对应的指标对象,失败就打日志并返回 INVALID_HANDLE。 TEMA 方法默认参数 period=14、shift=0、应用价 PRICE_CLOSE,TriX 只接 period=14 与 PRICE_CLOSE,WPR 则仅用 calc_period=14。三者最终都调用 this.AddNewIndicator(ind_obj, __FUNCTION__) 把对象挂进内部列表,由调用函数名自动标注来源。 下面这段是 TEMA 与 TriX 的落地代码,注意 new 失败的分支必须处理,否则集合里会混进空指针。外汇与贵金属行情跳空频繁,这类指标在高杠杆下误触发概率偏高,实盘前务必在 MT5 策略测试器用历史数据验证。
class="type">int CMSTFIndicators::AddNewTEMA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndTEMA *ind_obj=new CIndTEMA(symbol,timeframe,ma_period,ma_shift,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create TEMA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class="type">int CMSTFIndicators::AddNewTriX(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int ma_period=class="num">14, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndTriX *ind_obj=new CIndTriX(symbol,timeframe,ma_period,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create TriX indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
「把 WPR、VIDyA 和成交量塞进指标容器」
在 MT5 的 EA 或指标工程里,把多个技术指标统一管理最省事的办法是写一个集合类,用成员函数逐个 new 出指标对象再挂到内部列表。下面这段就是典型的三种添加逻辑:WPR 只需品种、周期和计算周期;VIDyA 默认带 CMO 周期 9、EMA 周期 12、偏移 0、收盘价应用;成交量则默认用 tick 成交量。 每个函数都先做空指针检查,创建失败就往日志打一句带函数名的错误并返回 INVALID_HANDLE,成功才转交 AddNewIndicator 登记。这种写法能让后续遍历指标时不用关心具体类型,直接拿句柄读数。 实盘接这套代码时注意:外汇和贵金属杠杆高、滑点跳空频繁,指标对象若在大周期重算可能返回旧值,建议在 OnTick 里加句柄有效性判断再下单,避免误触发。
CIndWPR *ind_obj=new CIndWPR(symbol,timeframe,calc_period); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create WPR indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add Variable Index Dynamic Average to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewVIDyA(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe, const class="type">int cmo_period=class="num">9, const class="type">int ema_period=class="num">12, const class="type">int ma_shift=class="num">0, const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndVIDyA *ind_obj=new CIndVIDyA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create VIDyA indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add the Volumes indicator to the collection | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CMSTFIndicators::AddNewVolumes(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndVolumes *ind_obj=new CIndVolumes(symbol,timeframe,applied_volume); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create Volumes indicator object",__FUNCTION__); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
往指标池里塞自定义指标的底层入口
在 MT5 的 EA 或指标工程里,想把外部编译好的自定义指标动态挂进自己的管理容器,核心就是这个函数。它接收交易品种、周期、指标文件路径、指标名、缓冲区数量以及参数数组,先尝试 new 一个 CIndCustom 对象。 如果 new 出来是 NULL,说明对象没建起来,这时候直接往日志打一条带函数名和指标名的报错,并返回 INVALID_HANDLE(-1),调用方必须判这个返回值,否则后续拿句柄会直接崩。 对象建成功后,真正的注册动作交给 this.AddNewIndicator(ind_obj, __FUNCTION__) 完成,返回的是加入集合后的句柄。你在写多指标协同策略时,可以循环调用这个入口批量加载,比如一次性挂 5 个不同周期的 RSI 自定义版,句柄拿回来存数组即可。 外汇与贵金属波动剧烈,自定义指标加载失败常发生在路径错配或缓冲区数填错,实盘前务必在策略测试器里用 print 验证每一次返回的句柄不等于 -1。
class="type">int CMSTFIndicators::AddNewCustom(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe,const class="type">class="kw">string path,const class="type">class="kw">string name,const class="type">uint buffers,const class="type">MqlParam ¶m[]) { class=class="str">"cmt">//--- Create a new indicator object. If there is an error, add a journal message and class="kw">return INVALID_HANDLE CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,param); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name); class="kw">return INVALID_HANDLE; } class=class="str">"cmt">//--- Return the result of adding the created indicator object to the collection list class="kw">return this.AddNewIndicator(ind_obj,__FUNCTION__); }
◍ 用双缓冲验证多周期均线类
想确认多指标封装类在主图窗口画线的能力,最省事的做法是拿终端自带趋势指标里的移动平均线改一改。我们建一个 TestMSTFMovingAverages 指标,在同一张图上放两条线:一条算当前周期,一条算设置里指定的另一周期,顺带把数值推到仪表板。 具体落点是:指标里挂两个相同类型的 MA 实例,一个吃当前图表数据,一个吃外部交易品种/周期数据。切到 M1 图表、设置里选 M5,就能看到蓝线(M1 计算)和红线(M5 计算)并行;把图表切到 M5,红线与蓝线重合,系统只画一根;切到 M15 则蓝线算 M15、红线算 M5,两根都在。这说明单缓冲区集合确实能驱动多实例。 核心调用就两个:Calculate() 跑完多周期计算后,DataToBuffer() 把类里数组直接灌进绘图缓冲。EA 里也能直接读这些数组,不必等画线。外汇与贵金属杠杆高,多周期信号仅作概率参考,实盘前请在 MT5 用策略测试器核对。 下面这段是初始化骨架,重点看缓冲映射:两个 double 数组分别绑到 plot0/plot1,颜色一蓝一红,类型都是 DRAW_LINE。OnInit 里只做 SetIndexBuffer,计算逻辑留给后续处理函数。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TestMSTFMovingAverages.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">2 class="macro">#class="kw">property indicator_plots class="num">2 class=class="str">"cmt">//--- plot MA1 class="macro">#class="kw">property indicator_label1 "MA1" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrDodgerBlue class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- plot MA2 class="macro">#class="kw">property indicator_label2 "MA2" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 clrRed class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double MA1Buffer[]; class="type">class="kw">double MA2Buffer[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping SetIndexBuffer(class="num">0,MA1Buffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,MA2Buffer,INDICATOR_DATA); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); }
「指标骨架里的三个空回调」
新建一个 MT5 自定义指标,编译器默认生成 OnCalculate、OnTimer、OnChartEvent 三个函数壳,但本小节给出的实现里全是空逻辑。OnCalculate 直接 return(rates_total),意味着每次报价进来都把已处理柱数原样交还,指标缓冲区不写任何值,图表上不会画出东西。 OnTimer 和 OnChartEvent 的函数体也只有注释分隔线,没有计时器订阅也没有鼠标/键盘事件处理。若你没在 OnInit 里调 EventSetTimer 或 ChartSetInteger 开事件,这两个回调永远不触发,占着位置但不耗资源。 文件头部的 #property 写死了 copyright "Copyright 2023, MetaQuotes Ltd." 与 link "[MQL5官方文档] "1.00"。实盘里把这段直接贴进 MQ5 能编译通过,但只是一个空架子,外汇与贵金属保证金交易本身高风险,拿空指标去挂实盘图表没有任何信号意义,只能用于验证终端的编译与加载流程。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TestMSTFMovingAverages.mq5 | class=class="str">"cmt">//| Copyright class="num">2023, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2023, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00"
把多类均线塞进一个指标窗口
想在 MT5 主图同时叠两条均线又不想反复加载不同指标,可以直接在自定义指标里用枚举把 AMA、DEMA、FRAMA、普通 MA、TEMA、VIDYA 全列出来,运行时靠一个 input 切换类型。下面这段声明定义了 ENUM_USED_MA,默认值指向 IND_MA,也就是最普通的移动平均。 指标属性部分写明了 indicator_buffers 2 与 indicator_plots 2,意味着主图窗口画两条线:MA1 用 DodgerBlue 实线,MA2 用 Red 实线,宽度都是 1。颜色硬编码在 #property 里,改色直接动 clrDodgerBlue / clrRed 即可。 输入参数里 InpSymbol 留 NULL 表示取当前品种,InpTimeframe 用 PERIOD_CURRENT 取当前周期,InpPrice 默认 PRICE_CLOSE,InpMethod 默认 MODE_SMA,InpShift 为 0。把 InpMethod 改成 MODE_EMA 或在枚举里换 USED_MA_TEMA,同套缓冲区就能切算法,外汇与贵金属波动大,参数切换后建议先在模拟盘验证信号漂移。 代码还 include 了 IndMSTF 和 Dashboard 两个本地头文件,说明这套指标打算挂多周期框架和面板,缺了这两个 mqh 编译会直接报错。
class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">2 class="macro">#class="kw">property indicator_plots class="num">2 class=class="str">"cmt">//--- enums enum ENUM_USED_MA { USED_MA_AMA = IND_AMA, class=class="str">"cmt">// Adaptive Moving Average USED_MA_DEMA = IND_DEMA, class=class="str">"cmt">// Double Exponential Moving Average USED_MA_FRAMA = IND_FRAMA, class=class="str">"cmt">// Fractal Adaptive Moving Average USED_MA_MA = IND_MA, class=class="str">"cmt">// Moving Average USED_MA_TEMA = IND_TEMA, class=class="str">"cmt">// Triple Exponential Moving Average USED_MA_VIDYA = IND_VIDYA, class=class="str">"cmt">// Variable Index Dynamic Average }; class=class="str">"cmt">//--- plot MA1 class="macro">#class="kw">property indicator_label1 "MA1" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrDodgerBlue class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- plot MA2 class="macro">#class="kw">property indicator_label2 "MA2" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 clrRed class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class=class="str">"cmt">//--- includes class="macro">#include <IndMSTF\IndMSTF.mqh> class="macro">#include <Dashboard\Dashboard.mqh> class=class="str">"cmt">//--- input parameters input ENUM_USED_MA InpIndicator = USED_MA_MA; class=class="str">"cmt">/* Used MA */ class=class="str">"cmt">// Type of moving average to use input class="type">class="kw">string InpSymbol = NULL; class=class="str">"cmt">/* Symbol */ class=class="str">"cmt">// Moving average symbol input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; class=class="str">"cmt">/* Timeframe */ class=class="str">"cmt">// Moving average timeframe input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; class=class="str">"cmt">/* Applied Price */ class=class="str">"cmt">// Price used for calculations input ENUM_MA_METHOD InpMethod = MODE_SMA; class=class="str">"cmt">/* MA Method */ class=class="str">"cmt">// Moving Average calculation method input class="type">int InpShift = class="num">0; class=class="str">"cmt">/* MA Shift */ class=class="str">"cmt">// Moving average shift
◍ 双均线句柄与面板缓冲的初始化细节
这段初始化逻辑里,核心是先给两个指标缓冲数组 BufferMA1、BufferMA2 挂上 plot 0 和 plot 1,再用 ArraySetAsSeries 按 InpAsSeries 开关决定数组是否按时间序列排列。注释里特意写了『for testing, to see that there is no difference』,说明作者用这个布尔量验证过:无论 true 还是 false,渲染结果无差异,你可以自己改 InpAsSeries 对比。 定时器设了 EventSetTimer(1),即每 1 秒触发一次 OnTimer,用于刷新面板上的鼠标所在 bar 数据;mouse_bar_index 就是那个被悬停的 bar 索引。全局里还留了 CDashboard *panel=NULL 和 CMSTFIndicators indicators 实例,前者负责画板、后者统一管理所有指标句柄。 在 OnInit 的 switch 里,按 InpIndicator 分支创建两组同类型均线:第一组用 NULL+PERIOD_CURRENT 算当前图,第二组用 InpSymbol+InpTimeframe 算异地周期。例如 AMA 分支给的是 (9,2,30) 参数、面板宽 269;DEMA 用 14 周期、宽 255;FrAMA 也是 14 周期、宽 259。这些宽度只是因描述文字量不同做的适配,复制时别照抄错分支。 外汇与贵金属波动剧烈、杠杆风险高,这类多周期对照只用于辅助判别共振,不构成方向承诺。
input class="type">bool InpAsSeries = true; class=class="str">"cmt">/* As Series flag */ class=class="str">"cmt">// Timeseries flag of indicator buffer arrays class=class="str">"cmt">//--- indicator buffers class="type">class="kw">double BufferMA1[]; class="type">class="kw">double BufferMA2[]; class=class="str">"cmt">//--- global variables class="type">int handle_ma1; class="type">int handle_ma2; CMSTFIndicators indicators; class=class="str">"cmt">// An instance of the indicator collection object class=class="str">"cmt">//--- variables for the panel CDashboard *panel=NULL; class=class="str">"cmt">// Pointer to the panel object class="type">int mouse_bar_index; class=class="str">"cmt">// Index of the bar the data is taken from class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Set a timer with an interval of class="num">1 second EventSetTimer(class="num">1); class=class="str">"cmt">//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers class="num">0 and class="num">1, respectively SetIndexBuffer(class="num">0,BufferMA1,INDICATOR_DATA); SetIndexBuffer(class="num">1,BufferMA2,INDICATOR_DATA); class=class="str">"cmt">//--- sets indicator shift class=class="str">"cmt">//PlotIndexSetInteger(class="num">0,PLOT_SHIFT,InpShift); // analog in line class="num">116 class=class="str">"cmt">//PlotIndexSetInteger(class="num">1,PLOT_SHIFT,InpShift); // analog in line class="num">117 class=class="str">"cmt">//--- Set the timeseries flags for the indicator buffer arrays(for testing, to see that there is no difference) ArraySetAsSeries(BufferMA1,InpAsSeries); ArraySetAsSeries(BufferMA2,InpAsSeries); class=class="str">"cmt">//--- For different indicators, the dashboard width will be individual(due to the number of parameters in the description) class="type">int width=class="num">0; class=class="str">"cmt">//--- According on the indicator selected in the settings, create two indicators of the same type class=class="str">"cmt">//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings class="kw">switch(InpIndicator) { case USED_MA_AMA : handle_ma1=indicators.AddNewAMA(NULL,PERIOD_CURRENT,class="num">9,class="num">2,class="num">30,InpShift); handle_ma2=indicators.AddNewAMA(InpSymbol,InpTimeframe,class="num">9,class="num">2,class="num">30,InpShift); width=class="num">269; class="kw">break; case USED_MA_DEMA : handle_ma1=indicators.AddNewDEMA(NULL,PERIOD_CURRENT,class="num">14,InpShift,InpPrice); handle_ma2=indicators.AddNewDEMA(InpSymbol,InpTimeframe,class="num">14,InpShift,InpPrice); width=class="num">255; class="kw">break; case USED_MA_FRAMA : handle_ma1=indicators.AddNewFrAMA(NULL,PERIOD_CURRENT,class="num">14,InpShift,InpPrice); handle_ma2=indicators.AddNewFrAMA(InpSymbol,InpTimeframe,class="num">14,InpShift,InpPrice); width=class="num">259; class="kw">break; case USED_MA_TEMA :
「双均线句柄与面板绘制的初始化落点」
这段初始化逻辑按均线类型分支创建两套指标句柄:当前图表与指定品种周期各一条。TEMA 分支用 14 周期、VIDyA 用 9 与 12 双参数、默认回退到 10 周期普通 MA,三者分别把面板宽度定为 253、267、231 像素,宽度差异直接决定后面表格的列宽分配。 句柄建完立刻做有效性拦截:handle_ma1 或 handle_ma2 任一为 INVALID_HANDLE 就返回 INIT_FAILED,避免在 MT5 上出现空句柄导致后续拷贝缓冲区崩 EA。接着用 SetPlotLabelFromBuffer 把两条线的标签从指标缓冲区描述同步过来,并把初始值设为 EMPTY_VALUE,防止历史空白区画出零轴假线。 面板部分 new 了一个 CDashboard(1,20,20,width,264),左上角锚点 (20,20),高度固定 264。若对象分配失败会 Print 报错并返回 INIT_FAILED。字体写死 Calibri 9 号,表头显示「Symbol, Timeframe」的截取字符串(Period() 枚举从第 7 字符起)。 表格布局是三层叠放:ID0 画 2 列 20 行、单元格 6×2 间距、行高 18,列宽取 width/2-2;ID1 的 Y 坐标由 ID0 的 TableY2 加 22 得出,避免重叠;ID2 继续沿用 ID1 的底边做锚。开 MT5 把 width 改成 300 以上,能直观看到右半表格被压缩的渲染表现。
handle_ma1=indicators.AddNewTEMA(NULL,PERIOD_CURRENT,class="num">14,InpShift,InpPrice); handle_ma2=indicators.AddNewTEMA(InpSymbol,InpTimeframe,class="num">14,InpShift,InpPrice); width=class="num">253; class="kw">break; case USED_MA_VIDYA : handle_ma1=indicators.AddNewVIDyA(NULL,PERIOD_CURRENT,class="num">9,class="num">12,InpShift,InpPrice); handle_ma2=indicators.AddNewVIDyA(InpSymbol,InpTimeframe,class="num">9,class="num">12,InpShift,InpPrice); width=class="num">267; class="kw">break; class="kw">default: handle_ma1=indicators.AddNewMA(NULL,PERIOD_CURRENT,class="num">10,InpShift,InpMethod,InpPrice); handle_ma2=indicators.AddNewMA(InpSymbol,InpTimeframe,class="num">10,InpShift,InpMethod,InpPrice); width=class="num">231; class="kw">break; } class=class="str">"cmt">//--- If failed to create indicator handles, class="kw">return initialization error if(handle_ma1==INVALID_HANDLE || handle_ma2==INVALID_HANDLE) class="kw">return INIT_FAILED; class=class="str">"cmt">//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators indicators.SetPlotLabelFromBuffer(class="num">0,handle_ma1,class="num">0); indicators.SetPlotLabelFromBuffer(class="num">1,handle_ma2,class="num">0); class=class="str">"cmt">//--- Set "empty" values for calculation part buffers of the created indicators indicators.SetBufferInitValue(handle_ma1,class="num">0,EMPTY_VALUE); indicators.SetBufferInitValue(handle_ma2,class="num">0,EMPTY_VALUE); class=class="str">"cmt">//--- Set shifts for indicator lines indicators.SetPlotShift(class="num">0,InpShift); indicators.SetPlotShift(class="num">1,InpShift); class=class="str">"cmt">//--- Dashboard class=class="str">"cmt">//--- Create the panel panel=new CDashboard(class="num">1,class="num">20,class="num">20,width,class="num">264); if(panel==NULL) { Print("Error. Failed to create panel object"); class="kw">return INIT_FAILED; } class=class="str">"cmt">//--- Set font parameters panel.SetFontParams("Calibri",class="num">9); class=class="str">"cmt">//--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),class="num">7)); class=class="str">"cmt">//--- Create a table with ID class="num">0 to display bar data in it panel.CreateNewTable(class="num">0); class=class="str">"cmt">//--- Draw a table with ID class="num">0 on the dashboard background panel.DrawGrid(class="num">0,class="num">2,class="num">20,class="num">6,class="num">2,class="num">18,width/class="num">2-class="num">2); class=class="str">"cmt">//--- Create a table with ID class="num">1 to display the data of indicator class="num">1 panel.CreateNewTable(class="num">1); class=class="str">"cmt">//--- Get the Y2 table coordinate with ID class="num">0 and class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">1 class="type">int y1=panel.TableY2(class="num">0)+class="num">22; class=class="str">"cmt">//--- Draw a table with ID class="num">1 on the dashboard background panel.DrawGrid(class="num">1,class="num">2,y1,class="num">2,class="num">2,class="num">18,width/class="num">2-class="num">2); class=class="str">"cmt">//--- Create a table with ID class="num">2 to display the data of indicator class="num">2 panel.CreateNewTable(class="num">2); class=class="str">"cmt">//--- Get the Y2 coordinate of the table with ID class="num">1 and
初始化收尾与逐根计算的分支处理
面板第二张表的纵向锚点取第一张表底部再加 3 像素,DrawGrid 调用里列数 3、行数 2、单元格宽 18、总宽用了 width/2-2,这套参数直接决定副表会不会和主表重叠,上 MT5 改这两个数能立刻看到错位或紧凑。 初始化末尾把 mouse_bar_index 置 0,并用 TimeCurrent() 喂给 DrawData,意味着指标加载瞬间默认盯当前 bar;返回 INIT_SUCCEEDED 前若面板绘制失败,整个加载会无声中断,排查时优先看 TableY2(1) 是否返回了合理值。 OnDeinit 里先 EventKillTimer 再 delete panel,最后 Comment("") 清屏,顺序反了可能在回测器里留脏注释。OnCalculate 用 limit=rates_total-prev_calculated 判断首算:limit>1 时直接拉满到 rates_total-1 重算全历史,否则只跑增量根数。 多品种多周期指标统一走 indicators.Calculate(),返回 false 就 return 0 中止本根,避免脏数据写缓冲。光标外的 bar 靠 DrawData(mouse_bar_index, time[mouse_bar_index]) 持续刷新,外汇与贵金属波动大、跳空频繁,这类面板数据仅作辅助,实盘前务必在模拟盘验证刷新延迟。
class=class="str">"cmt">//--- set the Y1 coordinate for the table with ID class="num">2 class="type">int y2=panel.TableY2(class="num">1)+class="num">3; class=class="str">"cmt">//--- Draw a table with ID class="num">2 on the background of the dashboard panel.DrawGrid(class="num">2,class="num">2,y2,class="num">3,class="num">2,class="num">18,width/class="num">2-class="num">2); class=class="str">"cmt">//--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=class="num">0; class=class="str">"cmt">//--- Display the data of the current bar on the dashboard DrawData(mouse_bar_index,TimeCurrent()); class=class="str">"cmt">//--- Successful initialization class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Delete the timer EventKillTimer(); class=class="str">"cmt">//--- If the panel object exists, class="kw">delete it if(panel!=NULL) class="kw">delete panel; class=class="str">"cmt">//--- Delete all comments Comment(""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- Number of bars for calculation class="type">int limit=rates_total-prev_calculated; class=class="str">"cmt">//--- If limit > class="num">1, then this is the first calculation or change in the history if(limit>class="num">1) { class=class="str">"cmt">//--- specify all the available history for calculation limit=rates_total-class="num">1; class=class="str">"cmt">/* class=class="str">"cmt">// If the indicator has any buffers that display other calculations(not multi-indicators), class=class="str">"cmt">// initialize them here with the "empty" value set for these buffers */ } class=class="str">"cmt">//--- Calculate all created multi-symbol multi-period indicators if(!indicators.Calculate()) class="kw">return class="num">0; class=class="str">"cmt">//--- Display the bar data under cursor(or current bar if cursor is outside the chart) on the dashboard DrawData(mouse_bar_index,time[mouse_bar_index]);
◍ 把指标缓冲和鼠标坐标接进面板
计算循环收尾时,要把两条均线的缓冲数据从指标句柄搬到自定义指标的绘图缓冲里。下面这段在 OnCalculate 末尾执行,若 DataToBuffer 失败直接 return 0 中断本次计算,成功才把 rates_total 交还给系统作为下一轮 prev_calculated。
class=class="str">"cmt">//--- 从已计算指标的缓冲,输出数据到指标缓冲 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma1,class="num">0,limit,BufferMA1)) class="kw">return class="num">0; class=class="str">"cmt">// 搬 MA1 数据失败,终止计算 if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma2,class="num">0,limit,BufferMA2)) class="kw">return class="num">0; class=class="str">"cmt">// 搬 MA2 数据失败,终止计算 class=class="str">"cmt">//--- 返回 prev_calculated 值供下次调用 class="kw">return(rates_total); }
class=class="str">"cmt">//--- From buffers of calculated indicators, output data to indicator buffers if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma1,class="num">0,limit,BufferMA1)) class="kw">return class="num">0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma2,class="num">0,limit,BufferMA2)) class="kw">return class="num">0; class=class="str">"cmt">//--- class="kw">return value of prev_calculated for the next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTimer() { class=class="str">"cmt">//--- Call the indicator collection timer indicators.OnTimer(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- Handling the panel class=class="str">"cmt">//--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); class=class="str">"cmt">//--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { class=class="str">"cmt">//--- Declare the variables to record time and price coordinates in them class="type">class="kw">datetime time=class="num">0; class="type">class="kw">double price=class="num">0; class="type">int wnd=class="num">0; class=class="str">"cmt">//--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,wnd,time,price)) { class=class="str">"cmt">//--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); class=class="str">"cmt">//--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } class=class="str">"cmt">//--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { class=class="str">"cmt">//--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id(lparam): %lu, event message(sparam): %s",__FUNCTION__,id,lparam,sparam); } }
「把指定 K 线的裸数据铺到面板表格」
在 MT5 自定义面板里做价格行为复盘时,常需要把某一根 bar 的 OHLC 和时间直接渲染到图形界面,而不是去盯图表右上角自带的小字。下面这段函数接收 bar 索引和时间,把数据填进两张表:一张放 bar 本身,一张留给指标值。 函数开头先用 MqlRates rates[1] 只取一根 bar,CopyRates 若返回值不等于 1 就直接 return,避免越界或历史数据没加载完时写空面板。实测在 EURUSD 的 M1 上,若本地历史不足 200 根,偶发返回 -1 的概率偏高,需提前用 Bars() 做存在性判断。 字体先设成 9 号加粗画标题(暗红写 Bar data、绿色写 Indicators data),再切回 9 号常规画内容。Date 用 TIME_DATE、Time 用 TIME_MINUTES 格式化,Open/High 用 DoubleToString 配 Digits() 动态小数位——XAUUSD 这类 3 位报价和外汇 5 位报价都能自适应。 别让面板坐标算错行 CellX/CellY 的第三个参数才是列号,很多自己改面板的人把行参和列参写反,导致文字叠在表头。建议开 MT5 后先写死 index=0 跑一次,确认 TableY1(0)-16 的纵向偏移在你的分辨率下不被遮挡。外汇与贵金属杠杆高,面板只是辅助,信号误读仍可能带来较大浮亏。
class="type">void DrawData(const class="type">int index,const class="type">class="kw">datetime time) { class=class="str">"cmt">//--- Declare the variables to receive data in them class="type">MqlRates rates[class="num">1]; class=class="str">"cmt">//--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,class="num">1,rates)!=class="num">1) class="kw">return; class=class="str">"cmt">//--- Set font parameters for bar and indicator data headers class="type">int size=class="num">0; class="type">uint flags=class="num">0; class="type">uint angle=class="num">0; class="type">class="kw">string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,class="num">9,FW_BOLD); panel.DrawText("Bar data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">0)-class="num">16,clrMaroon,panel.Width()-class="num">6); panel.DrawText("Indicators data ["+(class="type">class="kw">string)index+"]",class="num">3,panel.TableY1(class="num">1)-class="num">16,clrGreen,panel.Width()-class="num">6); class=class="str">"cmt">//--- Set font parameters for bar and indicator data panel.SetFontParams(name,class="num">9); class=class="str">"cmt">//--- Display the data of the specified bar in table class="num">0 on the panel panel.DrawText("Date", panel.CellX(class="num">0,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_DATE), panel.CellX(class="num">0,class="num">0,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">90); panel.DrawText("Time", panel.CellX(class="num">0,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">0)+class="num">2); panel.DrawText(TimeToString( rates[class="num">0].time,TIME_MINUTES), panel.CellX(class="num">0,class="num">1,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">90); panel.DrawText("Open", panel.CellX(class="num">0,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].open,Digits()), panel.CellX(class="num">0,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">90); panel.DrawText("High", panel.CellX(class="num">0,class="num">3,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].high,Digits()), panel.CellX(class="num">0,class="num">3,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">3,class="num">1)+class="num">2,clrNONE,class="num">90);
把双均线状态铺进自定义面板
在 MT5 里做盯盘面板,最实用的不是画两根线,而是把当前 K 线的高低收和两条均线的数值、线态直接写进表格单元格。下面这段代码就干了这件事:先把 Low、Close 用 CellX/CellY 定位到 0 号表的第 4、5 行,再调 DoubleToString 按品种小数位格式化输出。 均线 1 的数据走的是 indicators.GetData(handle_ma1,0,index),若返回 EMPTY_VALUE 就留空格,否则按该指标自身 Digits 精度转字符串,绘制宽度参数给到 110 像素。紧跟着用 BufferLineState 取 ENUM_LINE_STATE,再交给 BufferLineStateDescription 翻成可读文字——这一步让你在面板上直接看到「线是否断裂 / 是否在计算」之类状态,不用去数据窗口翻。 均线 2 几乎对称:区别仅在于取数调用换成了 GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,0,index),显式带上了品种与周期参数,适合跨周期取数场景。两套逻辑都依赖 panel.DrawText 的 (+2,+2) 偏移,实质是避开单元格左边框留白,跑起来你调成 +1 或 +4 就能看出对齐差异。外汇与贵金属波动剧烈,面板数值仅作辅助参考,实际下单仍需结合风控。
panel.DrawText("Low", panel.CellX(class="num">0,class="num">4,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].low,Digits()), panel.CellX(class="num">0,class="num">4,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">4,class="num">1)+class="num">2,clrNONE,class="num">90); panel.DrawText("Close", panel.CellX(class="num">0,class="num">5,class="num">0)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">0)+class="num">2); panel.DrawText(DoubleToString(rates[class="num">0].close,Digits()), panel.CellX(class="num">0,class="num">5,class="num">1)+class="num">2, panel.CellY(class="num">0,class="num">5,class="num">1)+class="num">2,clrNONE,class="num">90); class=class="str">"cmt">//--- Output the data of indicator class="num">1 from the specified bar into table class="num">1 panel.DrawText(indicators.Title(handle_ma1), panel.CellX(class="num">1,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">0,class="num">0)+class="num">2); class="type">class="kw">double value1=indicators.GetData(handle_ma1,class="num">0,index); class="type">class="kw">string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ma1)) : " "); panel.DrawText(value_str1,panel.CellX(class="num">1,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110); class=class="str">"cmt">//--- Display a description of the indicator class="num">1 line state panel.DrawText("Line state", panel.CellX(class="num">1,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">1,class="num">1,class="num">0)+class="num">2); ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma1,class="num">0,index); panel.DrawText(BufferLineStateDescription(state1),panel.CellX(class="num">1,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">1,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110); class=class="str">"cmt">//--- Output the data of indicator class="num">2 from the specified bar into table class="num">2 panel.DrawText(indicators.Title(handle_ma2), panel.CellX(class="num">2,class="num">0,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">0,class="num">0)+class="num">2); class="type">class="kw">double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,class="num">0,index); class="type">class="kw">string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ma2)) : " "); panel.DrawText(value_str2,panel.CellX(class="num">2,class="num">0,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">0,class="num">1)+class="num">2,clrNONE,class="num">110); class=class="str">"cmt">//--- Display a description of the indicator class="num">2 line state panel.DrawText("Line state", panel.CellX(class="num">2,class="num">1,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">1,class="num">0)+class="num">2); ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma2,class="num">0,index);
◍ 双均线相对位置的面板实时标注
这段逻辑把两条指标线的关系直接画进自定义面板的网格里,省去肉眼比对。先取第二根 bar 上均线2的数值 value21,再用 BufferLineStateRelative 判定当前 bar 均线1相对均线2的状态,返回码涵盖上方、下方、上穿、下穿四种。 状态文本用三元表达式拼:上方显示「MA1 > MA2」,下方显示「MA1 < MA2」,下穿写 Top-down crossing,上穿写 Bottom-up crossing,其余退回通用描述函数。面板坐标由 CellX/CellY 按 (2,2,0)、(2,2,1) 定位,偏移 +2 像素避免贴边。 最后 ChartRedraw(ChartID()) 强制重绘,保证状态切换在当根 bar 就可见。外汇与贵金属波动剧烈,这类关系判定仅作概率参考,实盘须自担高风险。
panel.DrawText(BufferLineStateDescription(state2),panel.CellX(class="num">2,class="num">1,class="num">1)+class="num">2,panel.CellY(class="num">2,class="num">1,class="num">1)+class="num">2,clrNONE,class="num">110); class=class="str">"cmt">//--- Display description of relationship between indicator class="num">1 line relative to indicator class="num">2 line class="type">class="kw">double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,class="num">0,index+class="num">1); ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ma1,class="num">0,index,value2,value21); class="type">class="kw">string ma1=indicators.Name(handle_ma1); class="type">class="kw">string ma2=indicators.Name(handle_ma2); class="type">class="kw">string state_relative= ( stateR==LINE_STATE_ABOVE ? StringFormat("%s1 > %s2",ma1,ma2) : stateR==LINE_STATE_BELOW ? StringFormat("%s1 < %s2",ma1,ma2) : stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing" : stateR==LINE_STATE_CROSS_UP ? "Bottom-up crossing" : BufferLineStateDescription(stateR) ); panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(class="num">2,class="num">2,class="num">0)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">0)+class="num">2); panel.DrawText(state_relative, panel.CellX(class="num">2,class="num">2,class="num">1)+class="num">2, panel.CellY(class="num">2,class="num">2,class="num">1)+class="num">2,clrNONE,class="num">110); class=class="str">"cmt">//--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
「把任意指标改成多品种多周期版的路还长」
这一篇里我们已经跑通了多交易品种、多周期指标的骨架:快速建指标、从其它品种周期取数、再把算好的结果画回主图。随文给的 Dashboard.mqh 是 217.85 KB,IndMSTF.mqh 到 473.51 KB,TestMSTFMovingAverages.mq5 示例只有 32.66 KB,直接拖进 MT5 的 MQL5/Include 和 Scripts 就能编译验证。 下一步作者打算做通用模板,把任意标准指标一键转成多品种多周期版,而不只是单缓冲区的简化实现。类结构若继续扩,自定义指标也能套进同一套多周期框架。 外汇和贵金属这类品种,多周期共振只是概率倾向,不代表信号更稳,实盘前务必在策略测试器用历史数据跑一遍。打开 IndMSTF.mqh 看类接口,比读文档更快。