扩展MQL5向导实现任意价位下单
用继承机制让EA支持计算价位挂单与动态止损止盈
一、MQL5向导与标准库的局限
MQL5标准库为开发大型严谨项目提供了基础框架,MQL5向导则能以对话模式在几分钟内将现成模块组装成EA,自动声明参数,极大节省时间。然而,通过向导基于标准类创建的EA能力有限:它只能从当前价位按固定距离下单、设置固定止损止盈,无法在动态计算的价位挂单。这种限制在长线交易中尤为明显,因为固定止损难以适应波动性变化,而挂单常需放在动态价位。
本文提出一种通用扩展方法:利用面向对象编程的继承与多态,创建标准类的子类替代向导生成代码中的标准类,从而保留向导与标准库优点,仅增加约四个字符串的代码量,便能让EA在要求的价位下单、设止损止盈。类似思路在《MQL5向导:如何教导EA在任意价位挂单》中有论述,但那篇文章强制改变从属过滤器参数,不利于多模块优化;本文方法在继承类中实现,模块间无冲突。
二、标准决策算法解析
向导生成的EA基于CExpert类实例,其内部声明CExpertSignal类指针(称主信号)。主信号包含指向从属过滤器(CExpertSignal的继承者)的指针。当无持仓和订单时,新报价到达会触发主信号检查开仓时机:它轮询从属滤波器,计算加权平均预测(方向),若绝对值超过阈值m_threshold_open,则将订单参数与bool条件传至EA。市价开仓或挂单距离由EA设置中的m_price_level、m_stop_level、m_take_level保存。
标准形态要求:当前品种无持仓,且加权平均预测绝对值超阈值(趋势极强)。这明显限制了应用领域——固定止损在多变波动中罕有成效,挂单也常需动态价位。信号模块只能生成预测值,主信号不处理价位,形成死结。
三、修改的决策算法与价格模块概念
为打破死结,引入新型信号模块“价格模块”(Price Module),能生成订单参数;训练主信号处理这些参数,选择最佳者传至EA。修改算法将趋势方向(加权平均预测)与入场点(价位)分离:先由过滤器确定方向,再从价格模块获取该方向的一组订单参数;若多个集合可用,取权重m_weight最大者;若方向确定但无入场点,EA不激活。
修改后开仓需满足:无持仓及其他订单;加权平均预测绝对值超阈值;至少一个开仓价被发现。该算法可处理多入场点,按方向筛选并选最优。这种分离使得EA具有选择性,方向已知但点位未到时可等待,提升策略适应性。
- 趋势方向由传统信号模块(过滤器)计算
- 入场价位、止损、止盈由价格模块计算
- 主信号整合两类模块,按权重选最优参数
- EA负责执行与订单更新
四、CExpertSignalAdvanced类详解
CExpertSignalAdvanced替换主信号角色,并成为价格模块基类。它声明保存订单参数的缓冲区变量(如m_order_open_long等),以及m_price_module索引,指向m_filters数组中首个价格模块。m_filters开头存标准过滤器指针,其后存价格模块,最多64个。类提供get开头方法获取保护成员。
class CExpertSignalAdvanced : public CExpertSignal
{
protected:
double m_order_open_long;
double m_order_stop_long;
double m_order_take_long;
datetime m_order_expiration_long;
double m_order_open_short;
double m_order_stop_short;
double m_order_take_short;
datetime m_order_expiration_short;
int m_price_module;
public:
CExpertSignalAdvanced();
~CExpertSignalAdvanced();
virtual void CalcPriceModuleIndex() {m_price_module=m_filters.Total();}
virtual bool CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration);
virtual bool CheckOpenShort(double &price,double &sl,double &tp,datetime &expiration);
virtual double Direction(void);
virtual double Prices(void);
virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration);
virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration);
virtual bool CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex);
virtual bool CheckUpdateOrderShort(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex);
double getOpenLong() { return m_order_open_long; }
double getOpenShort() { return m_order_open_short; }
double getStopLong() { return m_order_stop_long; }
double getStopShort() { return m_order_stop_short; }
double getTakeLong() { return m_order_take_long; }
double getTakeShort() { return m_order_take_short; }
datetime getExpLong() { return m_order_expiration_long; }
datetime getExpShort() { return m_order_expiration_short; }
double getWeight() { return m_weight; }
};
构造器初始化所有订单参数为EMPTY_VALUE,m_price_module为-1。CalcPriceModuleIndex()在添加首个价格模块时调用,将m_price_module设为当前数组总数。CheckOpenLong()若无价格模块则调父类;否则取方向,超阈值则Prices()更新参数并OpenLongParams传递。Direction()只循环索引0至m_price_module前的过滤器计算加权预测。Prices()遍历价格模块,按权重更新类变量,返回0。OpenLongParams()若开仓价非EMPTY_VALUE则传参返回true。CheckUpdateOrderLong()供EA修改已挂单。
bool CExpertSignalAdvanced::CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration)
{
if(m_price_module<0)
return(CExpertSignal::CheckOpenLong(price,sl,tp,expiration));
bool result =false;
double direction=Direction();
if(direction==EMPTY_VALUE)
return(false);
if(direction>=m_threshold_open)
{
Prices();
result=OpenLongParams(price,sl,tp,expiration);
}
return(result);
}
double CExpertSignalAdvanced::Prices(void)
{
m_order_open_long=EMPTY_VALUE;
m_order_stop_long=EMPTY_VALUE;
m_order_take_long=EMPTY_VALUE;
m_order_expiration_long=0;
m_order_open_short=EMPTY_VALUE;
m_order_stop_short=EMPTY_VALUE;
m_order_take_short=EMPTY_VALUE;
m_order_expiration_short=0;
int total=m_filters.Total();
double last_weight_long=0;
double last_weight_short=0;
for(int i=m_price_module;i<total;i++)
{
CExpertSignalAdvanced *prm=m_filters.At(i);
if(prm==NULL) continue;
if(prm.Prices()==EMPTY_VALUE)continue;
double weight=prm.getWeight();
if(weight==0.0)continue;
if(weight>last_weight_long && prm.getExpLong()>TimeCurrent())
if(prm.OpenLongParams(m_order_open_long,m_order_stop_long,m_order_take_long,m_order_expiration_long))
last_weight_long=weight;
if(weight>last_weight_short && prm.getExpShort()>TimeCurrent())
if(prm.OpenShortParams(m_order_open_short,m_order_stop_short,m_order_take_short,m_order_expiration_short))
last_weight_short=weight;
}
return(0);
}
五、CExpertAdvanced类与订单更新
CExpertAdvanced仅修改已放置订单的更新逻辑。它覆盖CheckTrailingOrderLong/Short,调用主信号的CheckUpdateOrderLong来检查修改,若需改则调UpdateOrder。UpdateOrder检查价格非EMPTY_VALUE则改单,否则删单。这样当价格模块发现更佳入场点,已挂单可动态更新。
class CExpertAdvanced : public CExpert
{
protected:
virtual bool CheckTrailingOrderLong();
virtual bool CheckTrailingOrderShort();
virtual bool UpdateOrder(double price,double sl,double tp,datetime ex);
public:
CExpertAdvanced();
~CExpertAdvanced();
};
bool CExpertAdvanced::UpdateOrder(double price,double sl,double tp,datetime ex)
{
ulong ticket=m_order.Ticket();
if(price==EMPTY_VALUE)
return(m_trade.OrderDelete(ticket));
return(m_trade.OrderModify(ticket,price,sl,tp,m_order.TypeTime(),ex));
}
六、开发价格模块实例
价格模块开发类似信号模块,但需覆盖Prices()而非LongCondition等。文中提供三例:基于增量之字折线指标、内柱线、外柱线。增量之字折线模块从指标缓冲区取入场级,找局部极值作止损,止损乘系数得止盈。内柱线无需指标,检测内柱线后按极值生成突破单。外柱线类似,检测吞噬形态。模块权重定义优先级,联合测试可行。
以DeltaZZ模块为例,描述符声明参数,类继承CExpertSignalAdvanced,构造器设省缺值,ValidationSettings检查参数,InitIndicators初始化指标,Prices()从缓冲区3、4取买卖价并更新参数。getBuySL找指标缓冲0最后非零值为止损。
double CPriceDeltaZZ::Prices(void)
{
double openbuy =m_deltazz.GetData(3,0);
double opensell=m_deltazz.GetData(4,0);
m_order_open_long=EMPTY_VALUE;
m_order_stop_long=EMPTY_VALUE;
m_order_take_long=EMPTY_VALUE;
m_order_expiration_long=0;
m_order_open_short=EMPTY_VALUE;
m_order_stop_short=EMPTY_VALUE;
m_order_take_short=EMPTY_VALUE;
m_order_expiration_short=0;
int digits=m_symbol.Digits();
if(openbuy>0)
{
m_order_open_long=NormalizeDouble(openbuy,digits);
m_order_stop_long=NormalizeDouble(getBuySL(),digits);
m_order_take_long=NormalizeDouble(m_order_open_long + m_tp_ratio*(m_order_open_long - m_order_stop_long),digits);
m_order_expiration_long=calcExpiration();
}
if(opensell>0)
{
m_order_open_short=NormalizeDouble(opensell,digits);
m_order_stop_short=NormalizeDouble(getSellSL(),digits);
m_order_take_short=NormalizeDouble(m_order_open_short - m_tp_ratio*(m_order_stop_short - m_order_open_short),digits);
m_order_expiration_short=calcExpiration();
}
return(0);
}
内柱线模块Prices()在无形态时返回EMPTY_VALUE;外柱线模块检测最高最低重叠,生成双向价位。测试显示三模块可独立或联合工作,权重分配影响优先级,系统按设计运行。
七、使用向导生成并修改EA步骤
首先将头文件CExpertAdvanced.mqh、CExpertSignalAdvanced.mqh存于MQL5/Include/Expert,价格模块存于MySignal,编译指标DeltaZigZag.ex5于Indicators。MetaEditor中选文件-新文件-智能程序(生成),命名后下一步。逐一加入模块:先加动量振荡器等信号模块,再加价格模块。注意顺序!然后下一步选资金管理,准备生成代码。
生成后编辑:头部将#include <Expert\Expert.mqh>改为ExpertAdvanced.mqh;将CExpert ExtExpert改为CExpertAdvanced;CExpertSignal *signal=new CExpertSignal改为CExpertSignalAdvanced;在AddFilter(filter1)后加signal.CalcPriceModuleIndex()。可删去固定缩进参数输入行使代码紧凑。附带的TEST_EA_AO_DZZ_IB_OB含注释可参考。
#include <Expert\ExpertAdvanced.mqh> CExpertAdvanced ExtExpert; CExpertSignalAdvanced *signal=new CExpertSignalAdvanced; signal.AddFilter(filter1); signal.CalcPriceModuleIndex();
八、总结与扩展价值
本文显著扩展MQL5向导应用:可开发在任意计算价位放置订单、止损、止盈的EA,支持多价格模块按权重选最优,方向已知点位未到则等待。继承机制引入变化最佳,无编程经验用户按指导也能编辑或创建高级EA。提供的三模块可启发更多算法,未来数量将增。此方法保持标准兼容,简化开发。
对比直接修改过滤器参数的旧方案,本文在继承类中实现无模块冲突,可安全用于向导优化。实际交易中,动态止损适应波动,挂单价位计算提升策略精度。建议读者基于文中基类编写自己的价格模块,如基于ATR通道或斐波那契级别,进一步释放向导潜力。