基于MQL5的自动化交易策略(第一部分):Profitunity系统(比尔·威廉姆斯的《交易混沌》)·综合运用
把分形与鳄鱼状态打印出来验证
调试比尔·威廉姆斯指标系统时,先确认最近一个分形有效再输出,避免拿到 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)
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 翻转平仓是否和颚线突破信号打架,再决定是否调手数或加过滤。
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 数据跑一遍断点。
obj_Trade.PositionClose(pos_ticket);
}
}
}
}用策略测试器压一压分形与鳄鱼阈值
EA 写完别急着挂真仓,先丢进 MT5 策略测试器跑一轮。这一步不是走形式,而是验证买卖信号是否真的按分形突破 + 鳄鱼线排列触发,顺便看程序在滑点和点差下的执行有没有变形。 我们做的初步优化只盯两个旋钮:分形确认根数、鳄鱼三条线的间隔阈值。把这两组参数在不同交易时段(亚盘 / 美盘)分别回测,能直接看出 EA 对行情节奏的敏感度差异。 从已出的回测结果看,阈值调宽后假信号明显减少,但进场频次同步下降;调窄则交易变多,止损被扫的概率倾向上升。外汇与贵金属杠杆高、波动突发行情多,任何参数组合都只是历史样本下的概率表现,不等于未来收益。 开 MT5 把分形参数从默认 5 改成 3 和 7 各跑一次,对比净值曲线斜率,比看任何文字说明都直观。
「句柄初始化这步别省」
把比尔·威廉姆斯盈利系统的分形、鳄鱼线、动量及加速振荡器拼进一个 EA,核心工作就是拿到每个指标的句柄并守住信号触发逻辑。作者在 MT5 策略测试器里跑过严格回测,目的是验证不同行情下信号响应与执行准确性,但回测通过不等于实盘复制,外汇和贵金属杠杆品种随时可能逆向跳空,风险始终偏高。 指标句柄声明时若不显式赋 INVALID_HANDLE,语法上能编译,但运行时若 iRSI 等函数返回失败,后续 CopyBuffer 会读到垃圾数据,策略逻辑被静默篡改。加上初始化检查,OnInit 直接返回 INIT_FAILED,EA 不加载,你马上知道哪根线断了。 下面这段是原文里作者回帖给初学者的对照写法,左边不初始化,右边先置无效值再判错:
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; }
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; }