EA如何用指标设交易条件
从句柄获取到执行模式全解析
为什么要在EA中使用指标判断交易条件
很多初学者写EA时习惯直接用价格突破或固定时间开仓,但这类逻辑在震荡行情中极易频繁止损。把技术指标引入EA,可以让程序根据趋势强度、超买超卖或通道突破等量化状态来决定是否下单,从而把人工看盘的规则固化成机器可执行的条件。本文延续MQL5 Cookbook系列,在前文交易函数库基础上,增强EA使其能从外部参数下拉选择指标,并依据指标值检查建仓条件。
原文提醒,本EA是在前文《交易历史和取得仓位属性的函数库》版本上修改的。由于此前代码已超1500行,每次加功能都越来越乱,因此逻辑上应当把代码拆成多个库文件。我们把主mq5放到TestIndicatorConditions文件夹,再建Include子文件夹存放mqh头文件,用MQL5向导或手动改后缀生成即可。
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);
}
获取指标句柄与复制缓冲区数据
要使用指标值,先拿句柄。标准指标用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);
}
交易品种执行模式与开仓函数改造
不同券商符号可能有即时执行(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 提供俄文原址与讨论。
- 主EA放 MQL5/Experts/TestIndicatorConditions
- 自定义指标放 MQL5/Indicators
- 编译只在主mq5,包含文件自动编译
- 优化前确认AllowedNumberOfSegments逻辑避免取数错