基于MQL5的自动化交易策略(第一部分):Profitunity系统(比尔·威廉姆斯的《交易混沌》)·综合运用
📘

基于MQL5的自动化交易策略(第一部分):Profitunity系统(比尔·威廉姆斯的《交易混沌》)·综合运用

第 3/3 篇

把分形与鳄鱼状态打印出来验证

调试比尔·威廉姆斯指标系统时,先确认最近一个分形有效再输出,避免拿到 0.0 的无效值。下面这段代码在 lastFractal_value 不为 0.0 且方向不是中性时才打印分形价与方向。 if (lastFractal_value != 0.0 && lastFractal_direction != FRACTAL_NEUTRAL){ Print("FRACTAL VALUE = ",lastFractal_value); Print("FRACTAL DIRECTION = ",getLastFractalDirection()); } //+------------------------------------------------------------------+

//FUNCTION TO GET FRACTAL DIRECTION

//+------------------------------------------------------------------+ string getLastFractalDirection(){ string direction_fractal = "NEUTRAL"; if (lastFractal_direction == FRACTAL_UP) return ("UP"); else if (lastFractal_direction == FRACTAL_DOWN) return ("DOWN"); return (direction_fractal); } getLastFractalDirection 默认回 NEUTRAL,只有明确的上/下分形才返回 UP/DOWN。实盘里若日志频繁刷 NEUTRAL,说明最近 N 根 K 线没形成标准分形,策略应暂停交易信号。 紧接着把鳄鱼三线与 AO、AC 当前状态打出来:Jaws/Teeth/Lips 取 [1] 号 bar 并用 _Digits 对齐小数位,AO 多留一位小数便于看微小动量。AC 颜色用 AC_COLOR_UP / AC_COLOR_DOWN 判断红绿。 Print("ALLIGATOR JAWS = ",NormalizeDouble(alligator_jaws[1],_Digits)); Print("ALLIGATOR TEETH = ",NormalizeDouble(alligator_teeth[1],_Digits)); Print("ALLIGATOR LIPS = ",NormalizeDouble(alligator_lips[1],_Digits)); Print("AO VALUE = ",NormalizeDouble(ao_values[1],_Digits+1)); if (ac_color[1] == AC_COLOR_UP){ Print("AC COLOR UP GREEN = ",AC_COLOR_UP); } else if (ac_color[1] == AC_COLOR_DOWN){ Print("AC COLOR DOWN RED = ",AC_COLOR_DOWN); } 价格与报价抓取也封装成函数:getClosePrice 用 iClose 取指定 bar 收盘价;getAsk/getBid 通过 SymbolInfoDouble 拿实时报价并 NormalizeDouble 到品种精度。外汇与贵金属杠杆高,报价跳动快,这类实时值只代表函数调用瞬间的快照,不能当成交价承诺。 string getLastFractalDirection(){ string direction_fractal = "NEUTRAL"; if (lastFractal_direction == FRACTAL_UP) return ("UP"); else if (lastFractal_direction == FRACTAL_DOWN) return ("DOWN"); return (direction_fractal); } //+------------------------------------------------------------------+

//FUNCTION TO GET CLOSE PRICES

//+------------------------------------------------------------------+ double getClosePrice(int bar_index){ return (iClose(_Symbol, _Period, bar_index)); } //+------------------------------------------------------------------+

//FUNCTION TO GET ASK PRICES

//+------------------------------------------------------------------+ double getAsk(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); } //+------------------------------------------------------------------+

//FUNCTION TO GET BID PRICES

//+------------------------------------------------------------------+ double getBid(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); } 最后一行给出了买入破位判定的雏形:当 1 号 bar 收盘价站上鳄鱼下巴(Jaws)时,isBreakdown_jaws_buy 置真。 bool isBreakdown_jaws_buy = alligator_jaws[1] < getClosePrice(1)

MQL5 / C++
if (lastFractal_value != class="num">0.0 && lastFractal_direction != FRACTAL_NEUTRAL){ class=class="str">"cmt">//--- Ensure fractal is valid
  Print("FRACTAL VALUE = ",lastFractal_value);
  Print("FRACTAL DIRECTION = ",getLastFractalDirection());
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|     FUNCTION TO GET FRACTAL DIRECTION                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string getLastFractalDirection(){
  class="type">class="kw">string direction_fractal = "NEUTRAL"; class=class="str">"cmt">//--- Default direction set to NEUTRAL
  if (lastFractal_direction == FRACTAL_UP) class="kw">return ("UP"); class=class="str">"cmt">//--- Return UP if last fractal was up
  else if (lastFractal_direction == FRACTAL_DOWN) class="kw">return ("DOWN"); class=class="str">"cmt">//--- Return DOWN if last fractal was down
  class="kw">return (direction_fractal); class=class="str">"cmt">//--- Return NEUTRAL if no specific direction
}
Print("ALLIGATOR JAWS = ",NormalizeDouble(alligator_jaws[class="num">1],_Digits));
Print("ALLIGATOR TEETH = ",NormalizeDouble(alligator_teeth[class="num">1],_Digits));
Print("ALLIGATOR LIPS = ",NormalizeDouble(alligator_lips[class="num">1],_Digits));
Print("AO VALUE = ",NormalizeDouble(ao_values[class="num">1],_Digits+class="num">1));
if (ac_color[class="num">1] == AC_COLOR_UP){
  Print("AC COLOR UP GREEN = ",AC_COLOR_UP);
}
else if (ac_color[class="num">1] == AC_COLOR_DOWN){
  Print("AC COLOR DOWN RED = ",AC_COLOR_DOWN);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|           FUNCTION TO GET CLOSE PRICES                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double getClosePrice(class="type">int bar_index){
  class="kw">return (iClose(_Symbol, _Period, bar_index)); class=class="str">"cmt">//--- Retrieve the close price of the specified bar
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|           FUNCTION TO GET ASK PRICES                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double getAsk(){
  class="kw">return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); class=class="str">"cmt">//--- Get and normalize the Ask price
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|           FUNCTION TO GET BID PRICES                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double getBid(){
  class="kw">return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); class=class="str">"cmt">//--- Get and normalize the Bid price
}
class="type">bool isBreakdown_jaws_buy = alligator_jaws[class="num">1] < getClosePrice(class="num">1) class=class="str">"cmt">//--- Check if breakdown for buy

「鳄鱼嘴突破叠加速度共振的开平逻辑」

这段逻辑把鳄鱼颚线(jaws)当作动态边界,用最近两根 K 线收盘价和颚线位置关系判定突破方向。isBreakdown_jaws_buy 要求颚线[1]低于收盘[1]且颚线[2]低于收盘[2],isBreakdown_jaws_sell 则反过来,属于典型的「价格站上/跌破颚线」判定。 买信号触发需要四个条件同时成立:最近分形为向下分形(FRACTAL_DOWN)、颚线向上突破成立、AC 柱[1]为上行色、AO 值[1]大于0且[2]小于0。卖信号镜像对称,分形为向上、颚线向下突破、AC 下行色、AO[1]小于0且[2]大于0。 下单手数写死为 0.01,买用 getAsk() 卖用 getBid(),属于最小仓位试单。外汇与贵金属杠杆高,这种固定微仓也只降低单笔风险,不消除方向误判概率。 平仓段不看信号方向,只盯 AO 柱翻转:AO[1]转负且[2]为正就清掉所有 BUY,AO[1]转正且[2]为负就清掉所有 SELL。循环里用 PositionGetTicket 取ticket再 PositionSelectByTicket 校验,只对 POSITION_TYPE_BUY 或 SELL 执行 PositionClose,避免误平对手仓。 开 MT5 把这段代码塞进 EA 的 OnTick,先跑模拟盘看 AO 翻转平仓是否和颚线突破信号打架,再决定是否调手数或加过滤。

MQL5 / C++
class="type">bool isBreakdown_jaws_buy = alligator_jaws[class="num">1] < getClosePrice(class="num">1) class=class="str">"cmt">//--- Check if breakdown for buy
                  && alligator_jaws[class="num">2] > getClosePrice(class="num">2);
class="type">bool isBreakdown_jaws_sell = alligator_jaws[class="num">1] > getClosePrice(class="num">1) class=class="str">"cmt">//--- Check if breakdown for sell
                  && alligator_jaws[class="num">2] < getClosePrice(class="num">2);

if (lastFractal_direction == FRACTAL_DOWN class=class="str">"cmt">//--- Conditions for Buy signal
   && isBreakdown_jaws_buy
   && ac_color[class="num">1] == AC_COLOR_UP
   && (ao_values[class="num">1] > class="num">0 && ao_values[class="num">2] < class="num">0)){
   Print("BUY SIGNAL GENERATED");
   obj_Trade.Buy(class="num">0.01,_Symbol,getAsk()); class=class="str">"cmt">//--- Execute Buy order
}
else if (lastFractal_direction == FRACTAL_UP class=class="str">"cmt">//--- Conditions for Sell signal
   && isBreakdown_jaws_sell
   && ac_color[class="num">1] == AC_COLOR_DOWN
   && (ao_values[class="num">1] < class="num">0 && ao_values[class="num">2] > class="num">0)){
   Print("SELL SIGNAL GENERATED");
   obj_Trade.Sell(class="num">0.01,_Symbol,getBid()); class=class="str">"cmt">//--- Execute Sell order
}

if (ao_values[class="num">1] < class="num">0 && ao_values[class="num">2] > class="num">0){ class=class="str">"cmt">//--- Condition to close all Buy positions
   if (PositionsTotal() > class="num">0){
      Print("CLOSE ALL BUY POSITIONS");
      for (class="type">int i=class="num">0; i<PositionsTotal(); i++){
         class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get position ticket
         if (pos_ticket > class="num">0 && PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Check if ticket is valid
            class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            if (pos_type == POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Close Buy positions
               obj_Trade.PositionClose(pos_ticket);
            }
         }
      }
   }
}
else if (ao_values[class="num">1] > class="num">0 && ao_values[class="num">2] < class="num">0){ class=class="str">"cmt">//--- Condition to close all Sell positions
   if (PositionsTotal() > class="num">0){
      Print("CLOSE ALL SELL POSITIONS");
      for (class="type">int i=class="num">0; i<PositionsTotal(); i++){
         class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get position ticket
         if (pos_ticket > class="num">0 && PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Check if ticket is valid
            class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            if (pos_type == POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Close Sell positions

◍ 用持仓单号平掉已有仓位

在 MT5 的 EA 逻辑里,平仓动作往往依赖一个具体的 position ticket(持仓单号),而不是靠品种名称或魔法码盲扫。上面这段截取的调用,本质是把之前遍历到的 pos_ticket 直接喂给交易对象的 PositionClose 方法。 实际验证时,你可以在 OnTradeTransaction 或手动遍历 PositionsTotal 拿到 ticket,再传入 obj_Trade.PositionClose(pos_ticket)。若返回 false,多半是单号已失效、品种点差突变或服务器拒绝——这时候得查 TradeResultRetcode,而不是反复发平仓指令。 外汇与贵金属属高风险品种,自动平仓逻辑一旦在滑点扩大时硬刚,可能以偏离预期的价格成交,建议在策略测试器先用 2023 年 XAUUSD 的 M1 数据跑一遍断点。

MQL5 / C++
      obj_Trade.PositionClose(pos_ticket);
      }
    }
  }
}

用策略测试器压一压分形与鳄鱼阈值

EA 写完别急着挂真仓,先丢进 MT5 策略测试器跑一轮。这一步不是走形式,而是验证买卖信号是否真的按分形突破 + 鳄鱼线排列触发,顺便看程序在滑点和点差下的执行有没有变形。 我们做的初步优化只盯两个旋钮:分形确认根数、鳄鱼三条线的间隔阈值。把这两组参数在不同交易时段(亚盘 / 美盘)分别回测,能直接看出 EA 对行情节奏的敏感度差异。 从已出的回测结果看,阈值调宽后假信号明显减少,但进场频次同步下降;调窄则交易变多,止损被扫的概率倾向上升。外汇与贵金属杠杆高、波动突发行情多,任何参数组合都只是历史样本下的概率表现,不等于未来收益。 开 MT5 把分形参数从默认 5 改成 3 和 7 各跑一次,对比净值曲线斜率,比看任何文字说明都直观。

「句柄初始化这步别省」

把比尔·威廉姆斯盈利系统的分形、鳄鱼线、动量及加速振荡器拼进一个 EA,核心工作就是拿到每个指标的句柄并守住信号触发逻辑。作者在 MT5 策略测试器里跑过严格回测,目的是验证不同行情下信号响应与执行准确性,但回测通过不等于实盘复制,外汇和贵金属杠杆品种随时可能逆向跳空,风险始终偏高。 指标句柄声明时若不显式赋 INVALID_HANDLE,语法上能编译,但运行时若 iRSI 等函数返回失败,后续 CopyBuffer 会读到垃圾数据,策略逻辑被静默篡改。加上初始化检查,OnInit 直接返回 INIT_FAILED,EA 不加载,你马上知道哪根线断了。 下面这段是原文里作者回帖给初学者的对照写法,左边不初始化,右边先置无效值再判错:

MQL5 / C++
class=class="str">"cmt">//--- 在全局范围内
class="type">int m_handleRsi; class=class="str">"cmt">// 句柄未初始化
OR
class="type">int m_handleRsi = INVALID_HANDLE; class=class="str">"cmt">// 初始化句柄
class=class="str">"cmt">//--- 初始化时
m_handleRsi = iRSI(m_symbol, RSI_TF, RSI_PERIOD, RSI_APP_PRICE); class=class="str">"cmt">// 你可以直接初始化,然后继续
OR
m_handleRsi = iRSI(m_symbol, RSI_TF, RSI_PERIOD, RSI_APP_PRICE); class=class="str">"cmt">// 可以进行初始化和检查。这样更好
if (m_handleRsi == INVALID_HANDLE) {
   Print("Failed to initialize RSI indicator");
   class="kw">return false;
}
class=class="str">"cmt">// 因此,现在任何指标都能正常工作。我们来看看指标初始化失败的例子,虽然这种情况很少见。
class=class="str">"cmt">// 如果没有检查,则不会添加指标,因此策略逻辑将被篡改。
class=class="str">"cmt">// 对于检查者,程序将终止,避免错误策略。在 OnInit 事件处理程序中,将返回初始化失败,程序将无法运行。
class=class="str">"cmt">// 这样,用户就会知道有故障需要检查。如果没有检查,程序会运行,但如果需要失败指示器,逻辑就会失败。现在明白了吗?
class=class="str">"cmt">// 初始化逻辑如下:
class="type">int OnInit() {
   if (!(YOUR LOGIC) e.g. m_handleRsi == INVALID_HANDLE) {
      class="kw">return INIT_FAILED;
   }
   class="kw">return INIT_SUCCEEDED;
}
开 MT5 把这段贴进自己的 EA 框架,故意把品种名写错一次,看 OnInit 是否卡在 INIT_FAILED——能卡住,说明防护到位;卡不住,实盘里就可能在某个凌晨吞掉你一笔不该出的单。

MQL5 / C++
class=class="str">"cmt">//--- 在全局范围内
class="type">int m_handleRsi; class=class="str">"cmt">// 句柄未初始化
OR
class="type">int m_handleRsi = INVALID_HANDLE; class=class="str">"cmt">// 初始化句柄
class=class="str">"cmt">//--- 初始化时
m_handleRsi = iRSI(m_symbol, RSI_TF, RSI_PERIOD, RSI_APP_PRICE); class=class="str">"cmt">// 你可以直接初始化,然后继续
OR
m_handleRsi = iRSI(m_symbol, RSI_TF, RSI_PERIOD, RSI_APP_PRICE); class=class="str">"cmt">// 可以进行初始化和检查。这样更好
if (m_handleRsi == INVALID_HANDLE) {
   Print("Failed to initialize RSI indicator");
   class="kw">return false;
}
class=class="str">"cmt">// 因此,现在任何指标都能正常工作。我们来看看指标初始化失败的例子,虽然这种情况很少见。
class=class="str">"cmt">// 如果没有检查,则不会添加指标,因此策略逻辑将被篡改。
class=class="str">"cmt">// 对于检查者,程序将终止,避免错误策略。在 OnInit 事件处理程序中,将返回初始化失败,程序将无法运行。
class=class="str">"cmt">// 这样,用户就会知道有故障需要检查。如果没有检查,程序会运行,但如果需要失败指示器,逻辑就会失败。现在明白了吗?
class=class="str">"cmt">// 初始化逻辑如下:
class="type">int OnInit() {
   if (!(YOUR LOGIC) e.g. m_handleRsi == INVALID_HANDLE) {
      class="kw">return INIT_FAILED;
   }
   class="kw">return INIT_SUCCEEDED;
}

常见问题

在初始化时创建分形与鳄鱼指标句柄,每根K线读取鳄鱼三线位置和最近分形值,用Comment或箭头对象标出,打开对应品种周期即可对照检查。
鳄鱼绿线在红蓝线上方且张口扩大,同时分形突破未被吞没,可视为倾向共振;用历史数据回看突破后3~5根K线的延续概率再做判断。
小布已内置这类价格行为诊断,打开品种页就能看到鳄鱼状态与分形突破提示,你把重复盯盘交给它,专注决策即可。
按单号平仓能精准退出特定信号触发的单子,避免误平手动单;回测时记录MagicNumber与ticket,出场只针对策略单号。
先跑默认参数看胜率基线,再±1~2格微调鳄鱼偏移,分形宽度保持原值;重点看样本外月份是否仍倾向正期望。