MQL5 酷客宝典 - 轴点交易信号·进阶篇
(2/3)· 从指标缓冲区到建仓确认,手把手拆开轴点信号的工程化实现路径
枢轴点信号类的接口与多头参数逻辑
在 MT5 里写自定义信号类时,把可调参数和确认流程拆成独立方法是避免后期改崩的关键。下面这段头文件声明给出了 CSignalPivots 的对外接口骨架:设置绘制的开关、邻近点数、市场模式权重,以及虚函数留给子类或框架去实现指标初始化与多空条件判定。 [CODE]void ToPlotMinor(const bool _to_plot) {m_to_plot_minor=_to_plot;} void PointsNear(const uint _near_pips); void Pattern_0(int _val) {m_pattern_0=_val;m_pattern_0_done=false;} virtual bool ValidationSettings(void); virtual bool InitIndicators(CIndicators *indicators); virtual int LongCondition(void); virtual int ShortCondition(void); virtual double Direction(void); virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration); protected: bool InitCustomIndicator(CIndicators *indicators); double Pivot(void) {return(m_pivots.GetData(0,0));} double MajorResistance(uint _ind); double MinorResistance(uint _ind); double MajorSupport(uint _ind); double MinorSupport(uint _ind);[/CODE] 逐行看:ToPlotMinor 直接写成员变量控制次级水平线是否绘制;PointsNear 用 uint 传 pips 数限定枢轴点附近多少点内算有效;Pattern_0 改权重时顺手把 m_pattern_0_done 置 false,强制下次重算。ValidationSettings 与 InitIndicators 是框架回调,LongCondition / ShortCondition 返回 int 而非 bool,说明多空信号是分级打分而非简单开关。 OpenLongParams 的实现片段暴露了实际挂单算法:若启用模式 0 且未完成,取 Ask 作基准,price 归一化到 base_price 减去 m_price_level 倍的 PriceLevelUnit()。此时 sl 被注释指向 Sup0.5 水平,意味着止损挂在次级支撑下方,具体数值由 MinorSupport 提供。外汇与贵金属波动大,这类基于枢轴点的挂单在数据行情中可能滑点超预期,验证时建议先用策略测试器跑 EURUSD 的 H1 看成交分布。
class="type">void ToPlotMinor(class="kw">const class="type">bool _to_plot) {m_to_plot_minor=_to_plot;} class="type">void PointsNear(class="kw">const class="type">uint _near_pips); class="type">void Pattern_0(class="type">int _val) {m_pattern_0=_val;m_pattern_0_done=class="kw">false;} class="kw">virtual class="type">bool ValidationSettings(class="type">void); class="kw">virtual class="type">bool InitIndicators(CIndicators *indicators); class="kw">virtual class="type">int LongCondition(class="type">void); class="kw">virtual class="type">int ShortCondition(class="type">void); class="kw">virtual class="type">class="kw">double Direction(class="type">void); class="kw">virtual class="type">bool OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration); class="kw">virtual class="type">bool OpenShortParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration); class="kw">protected: class="type">bool InitCustomIndicator(CIndicators *indicators); class="type">class="kw">double Pivot(class="type">void) {class="kw">return(m_pivots.GetData(class="num">0,class="num">0));} class="type">class="kw">double MajorResistance(class="type">uint _ind); class="type">class="kw">double MinorResistance(class="type">uint _ind); class="type">class="kw">double MajorSupport(class="type">uint _ind); class="type">class="kw">double MinorSupport(class="type">uint _ind);
◍ 用枢轴支撑阻力给多单定止损止盈
在 CExpertSignal 的买入参数函数里,止损先取 MinorSupport(0) 的 minor 支撑,若返回 DBL_MAX 说明当前没有有效支撑,直接 return false 放弃开仓。 拿到支撑价后必须用 m_symbol.NormalizePrice(sl) 按品种精度规整,否则部分外汇对(如 USDJPY 小数点后 3 位)会因报价精度不匹配被交易服务器拒单。 止盈则对接 MajorResistance(0) 的主要阻力,同样做 NormalizePrice 处理;过期时间叠加 m_expiration*PeriodSeconds(m_period) 秒数,最后把 params_set 和 m_pattern_0_done 置 true 表示模式 0 参数已就绪。 OpenLongParams 中若 m_general 为 -1(无基础过滤器),直接用 Ask 或 m_base_price 减 m_price_level*PriceLevelUnit 算挂单价,止损/止盈按 m_stop_level / m_take_level 对称偏移;外汇与贵金属杠杆高,枢轴位仅作概率参考,实盘需自验。 CSignalPivots 作为过滤器经 signal.AddFilter(filter0) 加入,并用 signal.General(0) 设为主过滤器,之后 LongCondition 会基于这些枢轴位判断多单触发条件。
sl=this.MinorSupport(class="num">0); if(sl==DBL_MAX) class="kw">return class="kw">false; class=class="str">"cmt">//--- 如果设置了止损价格 sl=m_symbol.NormalizePrice(sl); class=class="str">"cmt">//--- 获利价格 - Res1.class="num">0 水平 tp=this.MajorResistance(class="num">0); if(tp==DBL_MAX) class="kw">return class="kw">false; class=class="str">"cmt">//--- 如果设置了获利价格 tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); class=class="str">"cmt">//--- 如果设置了价格 params_set=true; class=class="str">"cmt">//--- 模式结束 m_pattern_0_done=true; } class=class="str">"cmt">//--- class="kw">return params_set; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 侦测用于买入的水平 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CExpertSignal::OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration) { CExpertSignal *general=(m_general!=-class="num">1) ? m_filters.At(m_general) : NULL; class=class="str">"cmt">//--- if(general==NULL) { class=class="str">"cmt">//--- 如果没有明确指定基础价格,就是用当前市场价格 class="type">class="kw">double base_price=(m_base_price==class="num">0.0) ? m_symbol.Ask() : m_base_price; price =m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); sl =(m_stop_level==class="num">0.0) ? class="num">0.0 : m_symbol.NormalizePrice(price-m_stop_level*PriceLevelUnit()); tp =(m_take_level==class="num">0.0) ? class="num">0.0 : m_symbol.NormalizePrice(price+m_take_level*PriceLevelUnit()); expiration+=m_expiration*PeriodSeconds(m_period); class="kw">return(true); } class=class="str">"cmt">//--- class="kw">return(general.OpenLongParams(price,sl,tp,expiration)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- CSignalPivots 过滤器 CSignalPivots *filter0=new CSignalPivots; if(filter0==NULL) { class=class="str">"cmt">//--- 错误 PrintFormat(__FUNCTION__+": 创建 filter0 出错"); class="kw">return INIT_FAILED; } signal.AddFilter(filter0); signal.General(class="num">0); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查买入条件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSignalPivots::LongCondition(class="type">void) { class="type">int result=class="num">0; class=class="str">"cmt">//--- 如果没有使用模式 class="num">0
「轴点接触判定与方向权重重置」
在自定义信号类里,模式 0 的触发逻辑依赖日线开盘价与轴点的相对位置。当 m_daily_open_pr 大于 m_pivot_val 且上一根柱最低价 last_low 落入轴点加阈值 m_pnt_near 的范围内,才认为价格从上方接触了轴点,并把 result 置为 m_pattern_0,同时向日志打印价格、轴点与阈值三位小数对齐的数值。
下方 Direction() 方法负责维护模式完成的生命周期。它通过 CopyRates(_Symbol, PERIOD_D1, 0, 1, daily_rates) 取 1 根日线,若返回小于 0 直接返回 0.0 避免误判。
若 m_pattern_0_done 为 true,且 isNewBar 识别到新日线,就把完成标记复位并返回 0.0,相当于每天只让模式 0 生效一次。未完成时则把 m_daily_open_pr 同步为当日开盘价,供上方接触判断使用。外汇与贵金属波动剧烈,这类轴点接触只是概率信号,实盘前请在 MT5 用策略测试器跑一遍观察触发频率。
if(IS_PATTERN_USAGE(class="num">0)) class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成 if(!m_pattern_0_done) class=class="str">"cmt">//--- 如果一天的开盘价高于轴点 if(m_daily_open_pr>m_pivot_val) { class=class="str">"cmt">//--- 当前柱的最低价格 class="type">class="kw">double last_low=m_low.GetData(class="num">1); class=class="str">"cmt">//--- 如果得到了价格 if((last_low>WRONG_VALUE) && (last_low<DBL_MAX)) class=class="str">"cmt">//--- 如果有上方的接触 (考虑到阈值) if(last_low<=(m_pivot_val+m_pnt_near)) { result=m_pattern_0; class=class="str">"cmt">//--- 写到日志中 Print("\n---== 价格在上方接触到轴点水平 ==---"); PrintFormat("价格: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",last_low); PrintFormat("轴点: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val); PrintFormat("阈值: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near); } } class=class="str">"cmt">//--- class="kw">return result; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 定义 "有权重的" 方向 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSignalPivots::Direction(class="type">void) { class="type">class="kw">double result=class="num">0.; class=class="str">"cmt">//--- 接收每日历史数据 class="type">MqlRates daily_rates[]; if(CopyRates(_Symbol,PERIOD_D1,class="num">0,class="num">1,daily_rates)<class="num">0) class="kw">return class="num">0.; class=class="str">"cmt">//--- 如果模式 class="num">0 已经完成 if(m_pattern_0_done) { class=class="str">"cmt">//--- 检查新的一天 if(m_day_new_bar.isNewBar(daily_rates[class="num">0].time)) { class=class="str">"cmt">//--- 重设模式完成标记 m_pattern_0_done=class="kw">false; class="kw">return class="num">0.; } } class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成 else { class=class="str">"cmt">//--- 每日开盘价 if(m_daily_open_pr!=daily_rates[class="num">0].open) m_daily_open_pr=daily_rates[class="num">0].open; class=class="str">"cmt">//--- 轴点
枢轴信号类的平仓与调度落点
CSignalPivots::CloseLongParams 只处理模式 0 未完成的场景:把平仓价直接设为当前 Bid,并往日志打印市场和价格。若 IS_PATTERN_USAGE(0) 成立且 m_pattern_0_done 为 false,函数返回 true,否则返回 false,意味着其余模式不走这套硬编码平仓逻辑。 CExpertSignal::CheckCloseLong 里有一行硬阈值判断:当 -m_direction >= m_threshold_close 时才置 result=true,并调用 CloseLongParams 取平仓价;若取价失败则回退为 false。这个 m_threshold_close 是关闭多单的方向强度门槛,调小它会让平仓信号更敏感。 主调度 CPivotsExpert::Processing 以分钟新柱为节拍:m_minute_new_bar.isNewBar() 不为真就直接 return false,不重复计算。新柱出现后先 m_signal.SetDirection(),无持仓走 CheckOpen,有持仓则进入后面的平仓分支(原文在此截断)。外汇与贵金属波动剧烈,这类基于枢轴的信号仅作概率参考,实盘前务必在 MT5 策略测试器用历史数据验证门槛参数。
class="type">class="kw">double curr_pivot_val=this.Pivot(); if(curr_pivot_val<DBL_MAX) if(m_pivot_val!=curr_pivot_val) m_pivot_val=curr_pivot_val; } class=class="str">"cmt">//--- 结果 result=m_weight*(this.LongCondition()-this.ShortCondition()); class=class="str">"cmt">//--- class="kw">return result; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 定义买入时的交易水平 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSignalPivots::CloseLongParams(class="type">class="kw">double &price) { price=class="num">0.; class=class="str">"cmt">//--- 如果使用了模式 class="num">0 if(IS_PATTERN_USAGE(class="num">0)) class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成 if(!m_pattern_0_done) { price=m_symbol.Bid(); class=class="str">"cmt">//--- 写到日志中 Print("\n---== 关闭买入仓位的信号 ==---"); PrintFormat("市场价格: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",price); class="kw">return true; } class=class="str">"cmt">//--- 返回结果 class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 生成关闭买入仓位的信号 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CExpertSignal::CheckCloseLong(class="type">class="kw">double &price) { class="type">bool result =class="kw">false; class=class="str">"cmt">//--- "禁止"信号 if(m_direction==EMPTY_VALUE) class="kw">return(class="kw">false); class=class="str">"cmt">//--- 检查是否超过阈值 if(-m_direction>=m_threshold_close) { class=class="str">"cmt">//--- 有信号 result=true; class=class="str">"cmt">//--- 尝试取得平仓水平 if(!CloseLongParams(price)) result=class="kw">false; } class=class="str">"cmt">//--- 基础价格清零 m_base_price=class="num">0.0; class=class="str">"cmt">//--- 返回结果 class="kw">return(result); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 主模块 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPivotsExpert::Processing(class="type">void) { class=class="str">"cmt">//--- 新的分钟柱 if(!m_minute_new_bar.isNewBar()) class="kw">return class="kw">false; class=class="str">"cmt">//--- 计算方向 m_signal.SetDirection(); class=class="str">"cmt">//--- 如果没有仓位 if(!this.SelectPosition()) { class=class="str">"cmt">//--- 仓位开启模块 if(this.CheckOpen()) class="kw">return true; } class=class="str">"cmt">//--- 如果有仓位
◍ 轴点下方接触的多头信号判定
在 CSignalPivots 类的 LongCondition 方法里,模式 0 的多头触发逻辑依赖三个前置条件:当日开盘价低于轴点值、模式 0 尚未完成标记、且当前柱(索引 1)最高价向上触碰轴点减去阈值 m_pnt_near 的区间。 代码用 m_high.GetData(1) 取上一根已完成 K 线的最高价,并校验其处于 (WRONG_VALUE, DBL_MAX) 有效区间;只有 last_high >= m_pivot_val - m_pnt_near 时才将 result 置为 m_pattern_0,同时向日志打印价格、轴点与阈值三位小数对齐的现场数据,方便在 MT5 Experts 标签页核对接触精度。 OpenLongParams 负责回填具体下单参数,初始把 sl 和 tp 都预置为 WRONG_VALUE,仅在 IS_PATTERN_USAGE(0) 且 !m_pattern_0_done 时继续填充价格。外汇与贵金属杠杆品种波动剧烈,轴点接触仅代表概率倾斜,实盘前应在策略测试器用至少 3 个月 Tick 数据验证阈值敏感性。
else { class=class="str">"cmt">//--- 仓位关闭模块 if(this.CheckClose()) class="kw">return true; } class=class="str">"cmt">//--- 如果没有交易操作 class="kw">return class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查卖出条件 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CSignalPivots::LongCondition(class="type">void) { class="type">int result=class="num">0; class=class="str">"cmt">//--- 如果没有使用模式 class="num">0 if(IS_PATTERN_USAGE(class="num">0)) class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成 if(!m_pattern_0_done) class=class="str">"cmt">//--- 如果一天的开盘价低于轴点 if(m_daily_open_pr<m_pivot_val) { class=class="str">"cmt">//--- 当前柱的最高价 class="type">class="kw">double last_high=m_high.GetData(class="num">1); class=class="str">"cmt">//--- 如果得到了价格 if((last_high>WRONG_VALUE) && (last_high<DBL_MAX)) class=class="str">"cmt">//--- 如果有上方的接触 (考虑到阈值) if(last_high>=(m_pivot_val-m_pnt_near)) { result=m_pattern_0; class=class="str">"cmt">//--- 写到日志中 Print("\n---== 价格从下方接触到轴点水平 ==---"); PrintFormat("Price: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",last_high); PrintFormat("轴点: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val); PrintFormat("阈值: %class="num">0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near); } } class=class="str">"cmt">//--- class="kw">return result; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 定义买入交易的水平 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSignalPivots::OpenLongParams(class="type">class="kw">double &price,class="type">class="kw">double &sl,class="type">class="kw">double &tp,class="type">class="kw">datetime &expiration) { class="type">bool params_set=class="kw">false; sl=tp=WRONG_VALUE; class=class="str">"cmt">//--- 如果使用了模式 class="num">0 if(IS_PATTERN_USAGE(class="num">0)) class=class="str">"cmt">//--- 如果模式 class="num">0 没有完成 if(!m_pattern_0_done) { class=class="str">"cmt">//--- 开盘价 - 市场
「用支撑阻力给挂单定止损止盈」
这段逻辑落在挂单参数组装的收口处:先抓当前 Ask 作基准,再按价位单位往下推挂单价,随后把止损锚在 MajorSupport(0)、止盈锚在 MinorResistance(0)。 double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); sl=this.MajorSupport(0); if(sl==DBL_MAX) return false; sl=m_symbol.NormalizePrice(sl); tp=this.MinorResistance(0); if(tp==DBL_MAX) return false; tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); params_set=true; m_pattern_0_done=true; 两个取值都做了 DBL_MAX 守卫——拿不到有效支撑或阻力就直接 return false,不会带着空止损去下单。NormalizePrice 调用保证价格符合品种最小变动位数,否则 MT5 会拒单。 expiration 用 PeriodSeconds(m_period) 把周期换算成秒再累加,避免手动填错时间尺度。params_set 与 m_pattern_0_done 置 true 后函数把 params_set 返回,外层据此判断能否发单。外汇与贵金属杠杆高,支撑阻力被扫后反转概率不低,实盘前建议在策略测试器用历史数据跑一遍该分支。
class="type">class="kw">double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); class=class="str">"cmt">//--- 止损价格e - Sup1.class="num">0 水平 sl=this.MajorSupport(class="num">0); if(sl==DBL_MAX) class="kw">return class="kw">false; class=class="str">"cmt">//--- 如果设置了止损价格 sl=m_symbol.NormalizePrice(sl); class=class="str">"cmt">//--- 获利价格 - Res0.class="num">5 水平 tp=this.MinorResistance(class="num">0); if(tp==DBL_MAX) class="kw">return class="kw">false; class=class="str">"cmt">//--- 如果设置了获利价格 tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); class=class="str">"cmt">//--- 如果设置了价格 params_set=true; class=class="str">"cmt">//--- 模式结束 m_pattern_0_done=true; } class=class="str">"cmt">//--- class="kw">return params_set; } class=class="str">"cmt">//+------------------------------------------------------------------+