DoEasy 函数库中的时间序列(第四十六部分):多周期、多品种指标缓冲区·进阶篇
(2/3)· 从单周期到任意品种跨周期取数,缓冲区对象类如何少改代码多扛任务
缓冲区绘图属性的批量绑定
在自定义指标里,每个绘图缓冲区的外观与空值规则都靠 PlotIndexSet 系列函数落地。下面这段把位移、线型、线宽、颜色、空值、标签一次性从基类属性取出来塞进绘图索引,省得手写重复代码。 ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE)); ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH)); this.SetColor((color)this.GetProperty(BUFFER_PROP_COLOR)); //--- Set real parameters of the graphical series ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE)); //--- Set string parameters of the graphical series ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL)); 前三条 SetInteger 分别控制 PLOT_SHIFT(水平偏移量,常用于把信号线右移避免未来函数观感)、PLOT_LINE_STYLE(实线/虚线枚举)、PLOT_LINE_WIDTH(像素宽度,MT5 里 1~5 有效)。SetColor 接的是 color 类型属性,空值用 PlotIndexSetDouble 的 PLOT_EMPTY_VALUE 标记,标签用 PlotIndexSetString 的 PLOT_LABEL 决定图表里显示的系列名。 类封装上,CBufferArrow 构造函数写死 BUFFER_STATUS_ARROW 和默认标签 "Arrows",CBufferLine 对应 BUFFER_STATUS_LINE 与 "Line",CBufferSection 同理。注意构造参数里数组维度传了 1 和 1,中间夹的 2 是缓冲区关联的基础数组序号偏移,改这里会直接决定哪条数据线被绘制。外汇与贵金属杠杆高,指标偏移或线宽设错只影响视觉,但信号右移过量可能让你误判入场时机,回测前务必在 MT5 可视化里核对。
::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_SHIFT)); ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_STYLE,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_STYLE)); ::PlotIndexSetInteger((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LINE_WIDTH,(ENUM_PLOT_PROPERTY_INTEGER)this.GetProperty(BUFFER_PROP_LINE_WIDTH)); this.SetColor((class="type">class="kw">color)this.GetProperty(BUFFER_PROP_COLOR)); class=class="str">"cmt">//--- Set real parameters of the graphical series ::PlotIndexSetDouble((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,this.GetProperty(BUFFER_PROP_EMPTY_VALUE)); class=class="str">"cmt">//--- Set class="type">class="kw">string parameters of the graphical series ::PlotIndexSetString((class="type">int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,this.GetProperty(BUFFER_PROP_LABEL));
「缓冲区绘制风格的子类封装」
在自定义指标里,不同绘图样式对应不同的缓冲区结构。把每种样式写成 CBuffer 的派生类,构造时直接传好状态常量与数组维度,后面调指标就少踩坑。 下面这几个类覆盖了从零轴直方图到 ZigZag、双色填充的常见画法。注意构造参数里第 5、6 个整型值分别代表绘图所需的基础数组数量和内部缓冲数量,比如 CBufferHistogram2 传了 2 和 3,说明它吃两个数据数组、内部开三个缓冲。 外汇与贵金属杠杆高,指标只是概率参考,实盘前务必在 MT5 策略测试器里用历史数据验证缓冲绑定是否正确。
CBuffer(BUFFER_STATUS_SECTION,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">1,class="num">2,class="num">1,"Section") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Buffer of the "Histogram from the zero line" drawing style | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBufferHistogram : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferHistogram(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_base_array) : CBuffer(BUFFER_STATUS_HISTOGRAM,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">1,class="num">2,class="num">2,"Histogram") {} class=class="str">"cmt">//+--------------------------------------------------------------------+ class=class="str">"cmt">//|Buffer of the "Histogram on two indicator buffers" drawing style | class=class="str">"cmt">//+--------------------------------------------------------------------+ class CBufferHistogram2 : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferHistogram2(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_base_array) : CBuffer(BUFFER_STATUS_HISTOGRAM2,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">2,class="num">3,class="num">8,"Histogram2 class="num">0;Histogram2 class="num">1") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Buffer of the ZigZag drawing style | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBufferZigZag : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferZigZag(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_base_array) : CBuffer(BUFFER_STATUS_ZIGZAG,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">2,class="num">3,class="num">1,"ZigZag class="num">0;ZigZag class="num">1") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Buffer of the "Color filling between two levels" drawing style | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBufferFilling : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferFilling(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_base_array) : CBuffer(BUFFER_STATUS_FILLING,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">2,class="num">2,class="num">1,"Filling class="num">0;Filling class="num">1") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Buffer of the Bars drawing style |
◍ 缓冲区的三种派生类怎么写
在 MT5 自定义指标里,把 K 线数据塞进绘图缓冲区,通常从 CBuffer 基类再派生出几个专用子类。下面这段实现给出了 Bars、Candles 与 Calculate 三类构造差异,直接能抄进你的指标工程。 CBufferBars 用 BUFFER_STATUS_BARS 状态、4 个数据列(Open/High/Low/Close),构造参数里第 5、6 位分别传了 5 和 2,对应每根 bar 的数组宽度与偏移约定;CBufferCandles 几乎同构,只是状态换成 BUFFER_STATUS_CANDLES、偏移参数为 1,标签也改成 Candle 前缀。 CBufferCalculate 则是纯计算缓冲,BUFFER_TYPE_CALCULATE 类型、只挂 1 列名为 Calculate 的数组,不负责直接绘图。注意原文里这个类名出现了两次,第二次去掉了高亮部分的 5 传参,实际编译应以无冗余参数的版本为准,否则可能触发重载歧义。 外汇与贵金属指标调用这些缓冲时波动剧烈,参数错配会导致图形错位,建议开 MT5 用样例符号先跑一轮肉眼核对。
class CBufferBars : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferBars(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_base_array) : CBuffer(BUFFER_STATUS_BARS,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">4,class="num">5,class="num">2,"Bar Open;Bar High;Bar Low;Bar Close") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Buffer of the Candles drawing style | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBufferCandles : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferCandles(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_base_array) : CBuffer(BUFFER_STATUS_CANDLES,BUFFER_TYPE_DATA,index_plot,index_base_array,class="num">4,class="num">5,class="num">1,"Candle Open;Candle High;Candle Low;Candle Close") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculated buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBufferCalculate : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferCalculate(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_array) : CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,class="num">1,class="num">1,class="num">0,"Calculate") {} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculated buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBufferCalculate : class="kw">public CBuffer { class="kw">private: class="kw">public: class=class="str">"cmt">//--- Constructor CBufferCalculate(class="kw">const class="type">uint index_plot,class="kw">const class="type">uint index_array) : CBuffer(BUFFER_STATUS_NONE,BUFFER_TYPE_CALCULATE,index_plot,index_array,class="num">1,class="num">1,class="num">0,"Calculate") {} class=class="str">"cmt">//--- Supported integer properties of a buffer class="kw">virtual class="type">bool SupportProperty(ENUM_BUFFER_PROP_INTEGER class="kw">property); class=class="str">"cmt">//--- Supported real properties of a buffer class="kw">virtual class="type">bool SupportProperty(ENUM_BUFFER_PROP_DOUBLE class="kw">property); class=class="str">"cmt">//--- Supported class="type">class="kw">string properties of a buffer
缓冲对象怎么把指标数据吃进来
CBufferCalculate 类里留了一组虚函数和 inline 方法,专门负责把外部指标缓冲区接管到自身 DataBuffer[0] 数组。SupportProperty 和 PrintShort 是给调试用的,前者声明支持哪种字符串属性,后者往日志打一句简短描述,方便在 MT5 Experts 标签里肉眼核对缓冲身份。 SetData 和 GetData 是最底层读写接口:写入时调 SetBufferValue(0, series_index, value),读取时从 GetDataBufferValue(0, series_index) 拿 double。注意两个方法都硬编码了缓冲区索引 0,说明这个类默认只管单缓冲序列,多缓冲指标得自己扩。 真正把数据拉满的是三个 FillAsSeries 重载,底层统一走 ::CopyBuffer。第一个按起始位置 start_pos 和条数 count 拷;第二个用 start_time 加 count;第三个给起止时间对 datetime 区间。三者返回的都是实际拷贝的元素个数,拷贝失败会返回 -1,调用方得自己判。 开 MT5 写个 EA 挂个 iMA 句柄,用第二个重载 FillAsSeries(ma_handle, 0, TimeCurrent()-3600, 100),就能把最近一小时、最多 100 根 K 的均线值灌进对象数组,外汇和贵金属品种波动跳空多,这种按时间取数比按位置更稳,但滑点和点差风险仍在。
class="kw">virtual class="type">bool SupportProperty(ENUM_BUFFER_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Display a class="type">class="kw">short buffer description in the journal class="kw">virtual class="type">void PrintShort(class="type">void); class=class="str">"cmt">//--- Set the value to the data buffer array class="type">void SetData(class="kw">const class="type">uint series_index,class="kw">const class="type">class="kw">double value) { this.SetBufferValue(class="num">0,series_index,value); } class=class="str">"cmt">//--- Return the value from the data buffer array class="type">class="kw">double GetData(class="kw">const class="type">uint series_index) class="kw">const { class="kw">return this.GetDataBufferValue(class="num">0,series_index); } class=class="str">"cmt">//--- Copy data of the specified indicator to the buffer object array class="type">int FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">int start_pos,class="kw">const class="type">int count); class="type">int FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">class="kw">datetime start_time,class="kw">const class="type">int count); class="type">int FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">class="kw">datetime start_time,class="kw">const class="type">class="kw">datetime stop_time); }; class="type">int CBufferCalculate::FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">int start_pos,class="kw">const class="type">int count) { class="kw">return ::CopyBuffer(indicator_handle,buffer_num,start_pos,count,this.DataBuffer[class="num">0].Array); } class="type">int CBufferCalculate::FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">class="kw">datetime start_time,class="kw">const class="type">int count) { class="kw">return ::CopyBuffer(indicator_handle,buffer_num,start_time,count,this.DataBuffer[class="num">0].Array); } class="type">int CBufferCalculate::FillAsSeries(class="kw">const class="type">int indicator_handle,class="kw">const class="type">int buffer_num,class="kw">const class="type">class="kw">datetime start_time,class="kw">const class="type">class="kw">datetime stop_time) { class="kw">return ::CopyBuffer(indicator_handle,buffer_num,start_time,stop_time,this.DataBuffer[class="num">0].Array); }
「跨周期取数时当前图表符号的坑」
在缓冲区对象跨周期取数时,第一段代码用 buffer.Symbol() 去取当前图表时序,逻辑上没问题;但第二段被标黄的那行直接用了全局 Symbol(),只认主图品种。若把指标挂在 XAUUSD 图上却给 EURUSD 的缓冲区算数据,series_current 会拿到错误(甚至空)时序,后面 bar_current 直接 NULL,函数返回 WRONG_VALUE。 这个函数核心做三件事:拿当前周期和缓冲区周期的时序指针、按索引定位当前 bar 并映射到缓冲周期所属 bar、用 PeriodSeconds 比值算一个周期里塞了几根小周期 bar(算出来是 0 就返回 1)。返回值是整数根数,调用方拿它去循环拷贝缓冲值。 实际排错时,打开 MT5 把这段贴进你的 CBuffersCollection 类,故意挂个与 buffer 不同品种的主图,断点跟到 series_current==NULL 那行,就能复现符号错配。外汇和贵金属跨周期计算滑点大、重绘风险高,这类底层取数错误可能让信号整体偏移,建议先在模拟盘验证。
class="type">int CBuffersCollection::GetBarsData(CBuffer *buffer,class="kw">const class="type">int series_index,class="type">int &index_bar_period) { class=class="str">"cmt">//--- Get timeseries of the current chart and the chart of the buffer timeframe CSeriesDE *series_current=this.m_timeseries.GetSeries(Symbol(),PERIOD_CURRENT); CSeriesDE *series_period=this.m_timeseries.GetSeries(buffer.Symbol(),buffer.Timeframe()); if(series_current==NULL || series_period==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Get the bar object of the current timeseries corresponding to the required timeseries index CBar *bar_current=series_current.GetBar(series_index); if(bar_current==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Get the timeseries bar object of the buffer chart period corresponding to the time the timeseries bar of the current chart falls into CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),NULL,series_period.Timeframe()); if(bar_period==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart index_bar_period=bar_period.Index(PERIOD_CURRENT); class=class="str">"cmt">//--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period class=class="str">"cmt">//--- and class="kw">return this value(class="num">1 if the result is class="num">0) class="type">int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe()); class="kw">return(num_bars>class="num">0 ? num_bars : class="num">1); }
◍ 跨周期定位与柱数折算的实现落点
在多周期分析里,经常需要把缓冲对象图表上的某根柱,映射回当前图表对应位置的索引。下面这段逻辑先通过 GetBarSeriesFirstFromSeriesSecond 拿到第一序列里、时间落在第二序列柱开盘时刻的那根柱对象,若返回空指针则直接给出 WRONG_VALUE 的错误值,避免后续计算越界。 index_bar_period=bar_period.Index(PERIOD_CURRENT); 这一行把定位到的柱转成当前周期下的索引,供上层调用。紧接着用 PeriodSeconds 分别除出两个周期的秒数,得到「缓冲周期一根柱涵盖了当前周期多少根柱」的整数倍率——比如 H1 对 M5 会算出 12,M15 对 M1 则是 15。 若折算结果为 0(极端小周期对齐异常),代码返回 1 而不是 0,保证调用方至少按单根柱处理。类里配套的 GetBar 重载和 IndexBarPeriodByBarCurrent 接口,让「按索引取柱 / 按时间取柱 / 跨序列反查」都能在同一套封装下完成,开 MT5 把这段塞进你自己的 CTimeseries 派生类,调一个 timeframe 参数就能验证映射是否如预期。
CBar *bar_period=m_timeseries.GetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,bar_current.Time(),buffer.Symbol(),series_period.Timeframe()); if(bar_period==NULL) class="kw">return WRONG_VALUE; class=class="str">"cmt">//--- Write down the bar index on the current timeframe which falls into the bar start time of the buffer object chart index_bar_period=bar_period.Index(PERIOD_CURRENT); class=class="str">"cmt">//--- Calculate the amount of bars of the current timeframe included into one bar of the buffer object chart period class=class="str">"cmt">//--- and class="kw">return this value(class="num">1 if the result is class="num">0) class="type">int num_bars=::PeriodSeconds(bar_period.Timeframe())/::PeriodSeconds(bar_current.Timeframe()); class="kw">return(num_bars>class="num">0 ? num_bars : class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return the bar object of the specified timeseries of the specified symbol of the specified position(class="num">1) by index, (class="num">2) by time class=class="str">"cmt">//--- bar object of the first timeseries corresponding to the bar open time on the second timeseries(class="num">3) by index, (class="num">4) by time CBar *GetBar(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int index,class="kw">const class="type">bool from_series=true); CBar *GetBar(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">class="kw">datetime bar_time); CBar *GetBarSeriesFirstFromSeriesSecond(class="kw">const class="type">class="kw">string symbol_first,class="kw">const ENUM_TIMEFRAMES timeframe_first,class="kw">const class="type">int index, class="kw">const class="type">class="kw">string symbol_second=NULL,class="kw">const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT); CBar *GetBarSeriesFirstFromSeriesSecond(class="kw">const class="type">class="kw">string symbol_first,class="kw">const ENUM_TIMEFRAMES timeframe_first,class="kw">const class="type">class="kw">datetime first_bar_time, class="kw">const class="type">class="kw">string symbol_second=NULL,class="kw">const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT); class=class="str">"cmt">//--- Return the bar index on the specified timeframe chart by the current chart&class="macro">#x27;s bar index | class="type">int IndexBarPeriodByBarCurrent(class="kw">const class="type">int series_index,class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe); class=class="str">"cmt">//--- Return the flag of opening a new bar of the specified timeseries of the specified symbol