EA如何用指标设交易条件
📊

EA如何用指标设交易条件

从句柄获取到执行模式全解析

技术分析基础 难度 · 进阶 2026-05-31 7 分钟阅读
#MQL5#EA开发#指标条件#交易执行

为什么要在EA中使用指标判断交易条件

很多初学者写EA时习惯直接用价格突破或固定时间开仓,但这类逻辑在震荡行情中极易频繁止损。把技术指标引入EA,可以让程序根据趋势强度、超买超卖或通道突破等量化状态来决定是否下单,从而把人工看盘的规则固化成机器可执行的条件。本文延续MQL5 Cookbook系列,在前文交易函数库基础上,增强EA使其能从外部参数下拉选择指标,并依据指标值检查建仓条件。

原文提醒,本EA是在前文《交易历史和取得仓位属性的函数库》版本上修改的。由于此前代码已超1500行,每次加功能都越来越乱,因此逻辑上应当把代码拆成多个库文件。我们把主mq5放到TestIndicatorConditions文件夹,再建Include子文件夹存放mqh头文件,用MQL5向导或手动改后缀生成即可。

✦ 拆分建议
当EA主文件超过千行,就把枚举、错误处理、信号、交易函数等按职责拆成.mqh,用#include引入,可大幅提升可读性与维护效率。

EA代码库的结构与包含方式

文章创建了以下包含文件:Enums.mqh放所有枚举;InfoPanel.mqh管信息面板与图形对象;Errors.mqh放错误码与去初始化原因;TradeSignals.mqh复制价格指标与信号模块;TradeFunctions.mqh交易函数;ToString.mqh数字转字符串;Auxiliary.mqh其他辅助。主文件用#include "Include\XXX.mqh"引入。头文件间若引用同层文件直接写名,上层用..\主文件。

#include "Include\Enums.mqh"
#include "Include\InfoPanel.mqh"
#include "Include\Errors.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"
#include "..\TestIndicatorConditions.mq5"
#include "Enums.mqh"
#include "InfoPanel.mqh"
#include "Errors.mqh"
#include "TradeSignals.mqh"
#include "ToString.mqh"
#include "Auxiliary.mqh"

这种结构让每个头文件各司其职。例如TradeFunctions.mqh专注下单修改,TradeSignals.mqh专注取数和信号。编译时只需在主mq5按F7,包含链会自动处理。注意路径反斜杠在MQ5字符串里要写双反斜杠。

外部参数设计与指标枚举

我们在Enums.mqh增加ENUM_INDICATORS,让用户从移动平均(MA)、商品通道指数(CCI)、自定义MultiRange_PCH(PCH)里选。主文件外部参数有Indicator选择、IndicatorPeriod周期、IndicatorSegments段数(原NumberOfBars改名,指指标须连续向上/向下的柱数)、VolumeIncreaseStep增量步长等。

enum ENUM_INDICATORS
  {
   MA       = 0, // 移动平均
   CCI      = 1, // CCI
   PCH      = 2  // 价格通道
  };
sinput   long              MagicNumber=777;
input    ENUM_INDICATORS   Indicator=MA;
input    int               IndicatorPeriod=5;
input    int               IndicatorSegments=2;
input    double            Lot=0.1;
input    double            StopLoss=50;
input    double            TakeProfit=100;
input    bool              Reverse=true;

关键细节:IndicatorSegments若设1,程序内部AllowedNumberOfSegments会强制为3,因为要判断买入必须满足最近三柱指标值递增(含当前完成柱与前两柱)。CorrectInputParameters()做此保护,避免取数不足导致数组越界。

void CorrectInputParameters()
  {
   if(AllowedNumberOfSegments<=0)
     {
      if(IndicatorSegments<=1) AllowedNumberOfSegments=3;
      if(IndicatorSegments>=5) AllowedNumberOfSegments=5;
      else AllowedNumberOfSegments=IndicatorSegments+1;
     }
  }

交易权限检查与实时环境校验

实盘前必须确认交易被允许。CheckTradingPermission()在实时模式下依次检查终端连接、MQL5程序级允许、终端级允许、账户交易允许、账户EA允许,任一项不通返回非0,OnTick中就会跳过本次交易,等下一柱再试。这能避免在无网络或禁自动交易时疯狂报错。

bool CheckTradingPermission()
  {
   if(IsRealtime())
     {
      if(!TerminalInfoInteger(TERMINAL_CONNECTED)) return(1);
      if(!MQL5InfoInteger(MQL5_TRADE_ALLOWED)) return(2);
      if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) return(3);
      if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) return(4);
      if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT)) return(5);
     }
   return(0);
  }
⚠ 回测也要注意
策略测试器虽不跑IsRealtime分支,但实盘部署前务必用脚本打印上述返回值,确认券商环境全为0,否则EA默默不开仓却无提示。

获取指标句柄与复制缓冲区数据

要使用指标值,先拿句柄。标准指标用iMA、iCCI等i前缀函数;自定义用iCustom。GetIndicatorHandle()按枚举赋值全局indicator_handle。若失败打印提示。拿到句柄后在GetIndicatorsData()中用CopyBuffer复制数值到动态数组。注意ArraySetAsSeries(true)使索引0为最新完成柱。

void GetIndicatorHandle()
  {
   if(Indicator==MA) indicator_handle=iMA(_Symbol,Period(),IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE);
   if(Indicator==CCI) indicator_handle=iCCI(_Symbol,Period(),IndicatorPeriod,PRICE_CLOSE);
   if(Indicator==PCH) indicator_handle=iCustom(_Symbol,Period(),"MultiRange_PCH",IndicatorPeriod);
   if(indicator_handle==INVALID_HANDLE) Print("获取指标句柄失败!");
  }
double indicator_buffer1[];
double indicator_buffer2[];
bool GetIndicatorsData()
  {
   if(indicator_handle!=INVALID_HANDLE)
     {
      if(Indicator==MA || Indicator==CCI)
        {
         ArraySetAsSeries(indicator_buffer1,true);
         if(CopyBuffer(indicator_handle,0,0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments) return(false);
        }
      if(Indicator==PCH)
        {
         ArraySetAsSeries(indicator_buffer1,true); ArraySetAsSeries(indicator_buffer2,true);
         if(CopyBuffer(indicator_handle,0,0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments ||
            CopyBuffer(indicator_handle,1,0,AllowedNumberOfSegments,indicator_buffer2)<AllowedNumberOfSegments) return(false);
        }
      return(true);
     }
   else GetIndicatorHandle();
   return(false);
  }

缓冲区索引来自MQL5参考或自定义指标源码;若没有源码,可在可视化测试观察哪条线满足条件来反推。PCH用了两个缓冲区,故buffer2也要复制。复制长度用AllowedNumberOfSegments,确保有足够柱判断。

信号函数:把指标状态转成买卖指令

GetSignal()纯看指标数组:MA/CCI下,若连续N段递减返回SELL,递增返回BUY;PCH则结合收盘价与通道缓冲。GetTradingSignal()进一步结合持仓:无仓直接采纳信号;有仓且允许反转或加仓步长达标才返信号。这样EA既能顺势开仓,也能在已有仓时逻辑加仓或反手。

ENUM_ORDER_TYPE GetSignal()
  {
   if(Indicator==MA || Indicator==CCI)
     {
      if(AllowedNumberOfSegments==3 && indicator_buffer1[1]<indicator_buffer1[2]) return(ORDER_TYPE_SELL);
      if(AllowedNumberOfSegments==3 && indicator_buffer1[1]>indicator_buffer1[2]) return(ORDER_TYPE_BUY);
     }
   if(Indicator==PCH)
     {
      if(close_price[1]<indicator_buffer2[1] && open_price[1]>indicator_buffer2[1]) return(ORDER_TYPE_SELL);
     }
   return(WRONG_VALUE);
  }
ENUM_ORDER_TYPE GetTradingSignal()
  {
   if(!pos.exists)
     {
      if(GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL);
      if(GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY);
     }
   if(pos.exists)
     {
      GetPositionProperties(P_TYPE);
      if(Indicator==MA || Indicator==CCI)
        {
         if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_SELL) return(ORDER_TYPE_SELL);
         if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_BUY) return(ORDER_TYPE_BUY);
        }
     }
   return(WRONG_VALUE);
  }
✦ 可读性技巧
把重复的判断抽成GetSignal(),避免在有仓无仓两块写同样条件,减少改一处漏一处的风险。

交易品种执行模式与开仓函数改造

不同券商符号可能有即时执行(Instant)或市场执行(Market)。结构体symbol_properties增加execution_mode字段,枚举补S_EXECUTION_MODE,初始化时从SYMBOL_TRADE_EXEMODE读取。OpenPosition()据此分支:即时执行可开仓同时带SL/TP;市场执行旧版需先开仓再PositionModify补止损,build 803后也可同时设。

struct symbol_properties
  {
   ENUM_SYMBOL_TRADE_EXECUTION execution_mode;
  };
void OpenPosition(double lot, ENUM_ORDER_TYPE order_type, double price, double sl, double tp, string comment)
  {
   trade.SetExpertMagicNumber(MagicNumber);
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT)
     { trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment); }
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      if(!pos.exists)
        {
         trade.PositionOpen(_Symbol,order_type,lot,price,0,0,comment);
         pos.exists=PositionSelect(_Symbol);
         if(pos.exists) trade.PositionModify(_Symbol,sl,tp);
        }
      else trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment);
     }
  }

评论里有人遇到Alpari UK模拟盘测试时SL/TP不出现,原因正是市场执行模式老客户端不随开仓带止损,必须修改仓才会写。用新build或即时执行符号则正常。这提醒我们部署前用OnInit打印execution_mode确认。

初始化、事件循环与资源释放

OnInit调用CorrectInputParameters、GetIndicatorHandle、CheckNewBar、GetPositionProperties、SetInfoPanel。OnDeinit在移除时删面板并IndicatorRelease句柄防漏。OnTick非新柱只刷新面板;新柱且权限0才取指标、取柱数据、交易块、跟踪止损。

int OnInit()
  {
   CorrectInputParameters();
   GetIndicatorHandle();
   CheckNewBar();
   GetPositionProperties(P_ALL);
   SetInfoPanel();
   return(0);
  }
void OnTick()
  {
   if(!CheckNewBar())
     { if(IsVisualMode()||IsRealtime()) { GetPositionProperties(P_ALL); SetInfoPanel(); } return; }
   else
     {
      if(CheckTradingPermission()==0)
        {
         if(!GetIndicatorsData()) return;
         GetBarsData(); TradingBlock(); ModifyTrailingStop();
        }
     }
   GetPositionProperties(P_ALL); SetInfoPanel();
  }
void OnDeinit(const int reason)
  {
   Print(GetDeinitReasonText(reason));
   if(reason==REASON_REMOVE)
     { DeleteInfoPanel(); IndicatorRelease(indicator_handle); }
  }

回测优化与文件部署

策略测试器设好货币对周期,载入附带的.set优化参数。双核优化约40分钟,优化图看盈利区质量。附件ZIP解压后把TestIndicatorConditions放Experts目录,MultiRange_PCH.mq5放Indicators目录,否则PCH句柄无效。原文链接 https://www.mql5.com/zh/articles/645 提供俄文原址与讨论。

⚠ 常见坑
iCustom名称必须与指标文件名或编译名完全一致;若指标未部署,indicator_handle为INVALID_HANDLE,GetIndicatorsData会反复尝试但不开仓。

常见问题

在枚举文件定义enum ENUM_INDICATORS{MA,CCI,PCH},主文件用input ENUM_INDICATORS Indicator=MA;,再在GetIndicatorHandle()里用iMA/iCCI/iCustom按枚举分支赋值句柄。
旧版市场执行不允许开仓同时带SL/TP,需先PositionOpen不带止损,再PositionModify补设;build 803后支持同时设。先用SymbolInfoInteger查SYMBOL_TRADE_EXEMODE确认模式。
原文IndicatorSegments设1时内部强制AllowedNumberOfSegments=3,因为判断递增/递减需最新完成柱及前两根,CopyBuffer长度不能小于3,否则数组越界无信号。
用iCustom(_Symbol,Period(),"指标名",参数)拿句柄;缓冲区索引看源码或可视化测试反推,再用CopyBuffer(句柄,缓冲索引,0,长度,数组)复制,数组需ArraySetAsSeries(true)。