
在单一工具上使用不同的 EA 交易进行交易时 ORDER_MAGIC 的使用
简介
在 MQL5 中,我们能够向挂单指定一个幻数,以便使用此信息来识别委托。这为在不同的 EA 交易之间进行互动以及开发更加复杂的系统开创了巨大的可能性。在本文中,我想向大家介绍幻数被贬低的机会。
但是在我们继续本文的主题之前,我们需要更好地理解幻数是怎么构成的。一个数字有何魔力呢?由什么来确定是哪个 EA 交易对它进行设置的?“奇迹”始于开发人员设置到类型 ulong 中的机会,该类型通过幻数声明。
类型 ulong 是最长的
如果我们详细观察整数类型 long,我们可以看到此类型的最大值非常非常大:
类型 |
字节数 |
最小值 |
最大值 |
C + + 语言中的对等类型 |
long |
8 |
-9 223 372 036 854 775 808 |
9 223 372 036 854 775 807 |
__int64 |
ulong |
8 |
0 |
18 446 744 073 709 551 615 |
unsigned __int64 |
表 1. 数据类型 long 和 ulong 的属性
但是类型 ulong 通过组合正的和负的尾数而超过了它。
因此,指定长度非常巨大,但以前是怎么使用它的呢?
在我使用 mql 4 的经验中,经常注意到很多开发人员对使用幻数进行编码没有感觉。幻数的使用很明智,但是其编码看起来非常愚蠢。对幻数 12345 的特性能说什么?几乎一半的开发团体都使用此幻数。另一半的团体使用幻数 55555、33333 和 77777,这几乎就是全部集合。我想提醒读者注意,与他的计算机有超过 1000 个 EA 交易不同,数字 1000 已经足以对您的所有 EA 交易的名称进行单独地编码。
1000 - 仅是 3 个完整的类别,那么,对于剩下的 15 个可采用类型 ulong 的完整类别,我们应该怎么办?答案十分简单:对它们进行编码。
维基百科对代码一词的解释:
因此,我们将制定规则。我建议将幻数的代码规定为不仅仅是 EA 交易的 ID,还应包括 EA 运行所在的工具。原因在于 EA 交易在 EURUSD 上运行(举例而言)并不意味着 EA 交易仅显示在该工具上的委托。我还认为它对编写 EA 交易的互动代码是非常有用的,例如“您的/外来的”等,这样在检查位置时,EA 交易能够理解当前代码是由一个友好的 EA 交易构建的。我认为这样将足以创建一个非常复杂的系统。
那么,让我们总结一下我们已经理解的:我们在系统中提供了什么机会:
- 两个或更多 EA 交易在一个工具上工作且互不干扰的可能性。
- 两个或更多 EA 交易在不同工具上工作且相互补充的可能性。
- 使用 EA 交易按工具识别委托的能力。
至此,任务已经设定,让我们现在开始其实施。
简单 EA 交易
起草简单 EA 交易的代码 - 例如,在移动方向保持仓位。我认为,决定解析幻数的读者已经阅读了《针对初学者以 MQL5 编写‘EA 交易’的分步指南》一文,如果没有,我强烈建议阅读,因为我将不再详细介绍 EA 交易的创建。基本而言,EA 交易将建立仓位一次,其他时候则调整仓位。因此,我们需要建立仓位,即用于放置交易请求(交易委托)的函数。
创建一个为我们计算参数以填充交易请求结构字段的辅助类。
//+------------------------------------------------------------------ //| 这个类提供辅助交易计算 | //+------------------------------------------------------------------ class CProvision { protected: MqlTradeRequest trades; // OrderSend 请求结构的指针 public: int TYPE(const double &v[]); // 判断类型,相对于移动的读数 double pricetype(int type); // 计算开仓位置, 相对于类型 double SLtype(int type); // 计算 计算止损位置,相对于类型 double TPtype(int type); // 计算时间表位置, 相对于类型 long spread(); // 返回当前金融工具点差 int SendOrder(ENUM_ORDER_TYPE type,double volume); }; //+------------------------------------------------------------------ //| | //+------------------------------------------------------------------ int CProvision::SendOrder(ENUM_ORDER_TYPE type,double volume) { trades.action =TRADE_ACTION_DEAL; // 实施操作类型 trades.magic =magic; // EA 印记 (magic number 的标识符) trades.symbol =_Symbol; // 交易工具的名称 trades.volume =volume; // 交易请求的交易量 trades.price =pricetype((int)type); // 价格 trades.sl =SLtype((int)type); // 订单止损位置 trades.tp =TPtype((int)type); // 订单获利位置 trades.deviation=(int)spread(); // 距请求价格的最大偏离点数 trades.type=type; // 订单类型 trades.type_filling=ORDER_FILLING_FOK; if(OrderSend(trades,res)){return(res.retcode);} return(-1); } //+------------------------------------------------------------------ //| 判断类型,相对于移动的读数 | //+------------------------------------------------------------------ int CProvision::TYPE(const double &v[]) { double t=v[0]-v[1]; if(t==0.0)t=1.0; return((int)(0.5*t/fabs(t)+0.5)); } //+------------------------------------------------------------------ //| 计算开仓位置, 相对于类型 | //+------------------------------------------------------------------ double CProvision::pricetype(int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==0)return(tick.ask); if(type==1)return(tick.bid); } return(-1); } //+------------------------------------------------------------------ //| 计算止损位置, 相对于类型 | //+------------------------------------------------------------------ double CProvision::SLtype(int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==0)return(tick.bid-SL*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); if(type==1)return(tick.ask+SL*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); } return(0); } //+------------------------------------------------------------------ //| 计算时间帧位置, 相对于类型 | //+------------------------------------------------------------------ double CProvision::TPtype(int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==0)return(tick.bid+TP*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); if(type==1)return(tick.ask-TP*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); } return(0); } //+------------------------------------------------------------------ //| 返回点差 | //+------------------------------------------------------------------ long CProvision::spread() { return(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)); }
在具有此类之后,我们可以毫无问题地编写一个简单 EA 交易的代码:
//+------------------------------------------------------------------ //| EA 代码 | //+------------------------------------------------------------------ //--- 输入参数 input ulong magic =1; // 标识号 input int SL =300; // 止损 input int TP =1000; // 获利 input int MA_Period =25; // MA 周期 input double lot =0.1; // 仓位持仓量 input int MA_shift =0; // 指标偏移 input ENUM_MA_METHOD MA_smooth =MODE_SMA; // 平滑类型 input ENUM_APPLIED_PRICE price =PRICE_OPEN; // 价格类型 //--- 我们将保存指标句柄 int MA_handle, // 指标句柄 type_MA, // MA 的指定方向类型 rezult; // 存放 OrderSend 操作的结果变量 double v[2]; // 接受 MA 数值的缓存区 MqlTradeResult res; // OrderSend 响应结构的指针 MqlTick tick; // 最后市场信息结构的指针 CProvision prov; //+------------------------------------------------------------------ //| EA 初始化函数 | //+------------------------------------------------------------------ int OnInit() { //--- 创建指标的句柄 MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,price); return(0); } //+------------------------------------------------------------------ //| EA 即时价格处理函数 | //+------------------------------------------------------------------ void OnTick() { if(CopyBuffer(MA_handle,0,0,2,v)<=0) {Print("#",magic,"复制错误");return;} type_MA=prov.TYPE(v); // 依据 MA 指示判断类型 if(PositionSelect(_Symbol))// 如果此时有开仓 { if(PositionGetInteger(POSITION_TYPE)!=type_MA)// 检查是否是平仓时机 { Print("#",magic,"标识号对应的仓位持仓量 ",PositionGetDouble(POSITION_VOLUME), " 反向仓位类型 ",PositionGetInteger(POSITION_TYPE)," 依据 ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,PositionGetDouble(POSITION_VOLUME)+lot); // 反向仓位 if(rezult!=-1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume); else{Print("#",magic,"错误",GetLastError()); return;} } } else // 如果没有仓位则开仓 { Print("#",magic,"标识号对应的仓位持仓量 ",PositionGetDouble(POSITION_VOLUME), " 开仓类型 ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot); // 开仓 if(rezult!=-1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume); else{Print("#",magic,"错误",GetLastError()); return;} } }
运行代码,确保 EA 交易在盈利能力方面没有差异,但是精确依据指定逻辑交易,这正是我们需要其提供的。
图 1. 一个 EA 交易在一个工具上的工作
现在,我们将尝试运行此 EA,但是在一个工具的不同时间框架上(出于经验,我们选择了一个随机工具 EURUSD:o)
图 2. 两个 EA 交易在相同工具不同时间框架上的冲突
因为两个 EA 交易都运行在一个工具上,并且代码没有指定仓位的共享,则两个 EA 交易都试图依据它们的指标读数纠正交易仓位,因此出现冲突。运行在 M1 上的 EA 交易试图调查单元格中的仓位,而其竞争对手试图阻止它。显然我们需要单独计算仓位,这正是我们现在要做的。
仓位或虚拟仓位?
因为在 MetaTrader 5 中,开始人员已经从委托转变为考虑仓位,更加详细地考虑与记录仓位有关的函数更为明智。
// 返回开仓数量. int PositionsTotal(); // 返回开仓列表中相应号码位置的交易品种. string PositionGetSymbol(int index); // 选择开仓用于将来工作. bool PositionSelect(string symbol, uint timeout=0); // 函数返回开仓的请求属性. double PositionGetDouble(ENUM_POSITION_PROPERTY property_id); // 函数返回开仓的请求属性. long PositionGetInteger(ENUM_POSITION_PROPERTY property_id); // 函数返回开仓的请求属性. string PositionGetString(ENUM_POSITION_PROPERTY property_id);
表 2-4 列出了获取兼容仓位属性 PositionGetDouble、PositionGetInteger 和 PositionGetString 的函数的枚举标识符。.
标识符 |
说明 |
类型 |
POSITION_VOLUME |
持仓量 |
double |
POSITION_PRICE_OPEN |
持仓价 |
double |
POSITION_SL |
未平仓位的止损价 |
double |
POSITION_TP |
未平仓位的获利价 |
double |
POSITION_PRICE_CURRENT |
交易品种的当前价 |
double |
POSITION_COMMISSION |
手续费 |
double |
POSITION_SWAP |
累积掉期 |
double |
POSITION_PROFIT |
当前盈利 |
double |
表 2. 枚举值 ENUM_POSITION_PROPERTY_DOUBLE
标识符 |
说明 |
类型 |
POSITION_TIME |
建仓时间 |
datetime |
POSITION_TYPE |
仓位类型 |
|
POSITION_MAGIC |
仓位的幻数(参见 ORDER_MAGIC) |
long |
POSITION_IDENTIFIER |
仓位的标识 - 这是指定给重新建立的仓位的唯一数字,在其整个寿命期间不会改变。仓位的交易量不会改变其 ID。 |
long |
表 3. 枚举值 ENUM_POSITION_PROPERTY_INTEGER
标识符 |
说明 |
类型 |
POSITION_SYMBOL |
为其建仓的交易品种 |
string |
POSITION_COMMENT |
仓位注释 |
string |
表 4. 枚举值 ENUM_POSITION_PROPERTY_STRING
我们可以从函数清晰地看到,语言不包含依据“谁输入委托”的原则进行的仓位分割,但是包含可以使用此类记录的可能性,因为ORDER_MAGIC、POSITION_MAGIC 和 DEAL_MAGIC 是完全相同的数字,取自用户指定的幻数。POSITION_MAGIC 取自 DEAL_MAGIC,用于建仓,而 DEAL_MAGIC 又取自委托的 ORDER_MAGIC。
可以毫无问题地识别委托、交易或仓位,但是不可能通过某个幻数建立一个仓位。现在,我们将尝试消除这个缺点。让我们创建内置函数的类似函数,但是通过幻数进行识别。声明一个用于依据幻数处理虚拟仓位的类。
因为我们有机会使用面向对象编程,让我们也声明我们的结构(获得客观编写代码的另一次练习)。
//+------------------------------------------------------------------ //| CPositionVirtualMagic 类结构 | //+------------------------------------------------------------------ struct SPositionVirtualMagic { double volume; // 虚拟仓位持仓量 ENUM_POSITION_TYPE type; // 虚拟仓位类型 }; //+--------------------------------------------------------------------------------+ //| 本类依 EA 的魔幻号计算虚拟仓位 | //+--------------------------------------------------------------------------------+ class CPositionVirtualMagic { protected: SPositionVirtualMagic pvm; public: double cVOLUME(){return(pvm.volume);} // 返回 EA 的虚拟仓位持仓量 ENUM_POSITION_TYPE cTYPE(){return(pvm.type);} // 返回 EA 的虚拟仓位类型 bool PositionVirtualMagic(ulong Magic, string symbol, datetime CurrentTime ); // 虚拟仓位的计算方法 返回虚拟仓位存在或不存在. private: void prHistory_Deals(ulong &buf[],int HTD); // 填充单号数组 }; //+-------------------------------------------------------------------------------------+ //| 虚拟仓位计算方法, 返回 true 如果有虚拟仓位. | //+-------------------------------------------------------------------------------------+ bool CPositionVirtualMagic::PositionVirtualMagic(ulong Magic, string symbol, datetime CurrentTime ) { int DIGITS=(int)-log10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)); if(DIGITS<0)DIGITS=0; ulong Dticket=0; int History_Total_Deals=-1; double volume=0,volume_BUY=0,volume_SELL=0; ulong DTicketbuf[]; do { if(HistorySelect(0,TimeCurrent())) { History_Total_Deals=HistoryDealsTotal(); prHistory_Deals(DTicketbuf,History_Total_Deals); } HistorySelect(0,TimeCurrent()); } while(History_Total_Deals!=HistoryDealsTotal()); for(int t=0;t<History_Total_Deals;t++) { Dticket=DTicketbuf[t]; if(HistoryDealSelect(Dticket)) { if(HistoryDealGetInteger(Dticket,DEAL_TIME)>=CurrentTime) { if(HistoryDealGetInteger(Dticket,DEAL_MAGIC)==Magic) { if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_BUY) { volume_BUY+=HistoryDealGetDouble(Dticket,DEAL_VOLUME); } else { if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_SELL) { volume_SELL+=HistoryDealGetDouble(Dticket,DEAL_VOLUME); } } } } } else{HistorySelect(0,TimeCurrent());t--;} // 如果失败, 加载历史数据并再次传递到此步 } volume=NormalizeDouble(volume_BUY-volume_SELL,DIGITS); if(volume<0)pvm.type=POSITION_TYPE_SELL; else { if(volume>0)pvm.type=POSITION_TYPE_BUY; } pvm.volume=fabs(volume); if(pvm.volume==0)return(false); else return(true); }
在上文中(给出 CProvision 类的代码的地方),没有解释所有代码的来源及目的,因为 EA 交易的开发并不是本文的主题。
但是我们将详细探讨 CPositionVirtualMagic 类。
类以结构的形式给出:
struct SPositionVirtualMagic
该结构用于接受计算结果,类中的这样一个全局声明,归功于 pvm(结构变量),此结构可以在任何地方,在类的任何方法中使用。
接下来是类的两个方法:
double cVOLUME(){return(pvm.volume);} // 返回 EA 的虚拟仓位持仓量 ENUM_POSITION_TYPE cTYPE() {return(pvm.type);} // 返回 EA 的虚拟仓位类型
这些方法被声明为公共方法,因此,通过调用的类变量,它们在程序中的任何地方都是可用的,专为输出请求位置中的结构值而设计。
这一部分还声明了以下方法:
bool PositionVirtualMagic(ulong Magic,string symbol,datetime CurrentTime);
这是类的主要函数,我们将进一步聚焦于其详细分析,同时,我将提前说明访问说明符 private(私有)下的函数:
void prHistory_Deals(ulong &buf[],int HTD);
此方法将交易的单证记录写入数组,基本上是一个循环,并且应在调用函数中说明,但是我希望减小函数 PositionVirtualMagic() 的大小(从而增加代码的可读性),因此我将此循环移出函数的限制,并且演示如何使用私有访问说明符。
因此让我们回到 PositionVirtualMagic()。此函数在其最开头有一行精度计算,用于将计算出来的持仓量的双精度值四舍五入到该精度。
int DIGITS=(int)-log10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)); if(DIGITS<0)DIGITS=0;
对于与零进行比较的运算,这是必需的,否则小数点后的第 8 位数字会阻止我们将 0 赋予某个值,从而导致执行错误。
持仓量被四舍五入到最小增量。如果最小增量大于 1,则按整数部分四舍五入。接下来是循环 while,但是以新的方式使用它(与在 mql4 中的不同),因为条件表达式的验证位于循环的末尾,而不是在开头:
do { if(HistorySelect(0,TimeCurrent())) { History_Total_Deals=HistoryDealsTotal(); prHistory_Deals(DTicketbuf,History_Total_Deals); } HistorySelect(0,TimeCurrent()); } while(History_Total_Deals!=HistoryDealsTotal());
此类方法非常有用,因为条件表达式在循环内计算,在循环启动时,尚未准备好此验证。
循环包含历史记录的加载,我想提醒读者注意,要确保处理历史记录的内置函数能够工作,这是必需条件。
HistorySelect(0,TimeCurrent())
我认为我应该解释我的变量名称选择体系。
细心的读者应该注意到类的名称含有初始字母 "C",这并不是语法所要求的,可以指定任意名称,但是这种方式使阅读更加容易。如果字母 "C" 出现在名称前面,则我们立即知道这是类的名称,如果字母为 "S" - 则它是一个结构。如果变量的值来自某些内置函数,则我简单地改变函数名称的内容,获得变量名称,例如:
CurrentTime = TimeCurrent();
简单且可读,我们可以立即明白变量包含的是什么。尤其是因为 MetaEditor 包含将某个代码片断拖到指定位置的功能。
进一步查看代码,我们看到在加载历史记录之后是调用函数:
History_Total_Deals=HistoryDealsTotal();
将存储的交易数量赋予变量。按照相同的条件,我们将实施现有循环的验证。我们需要验证什么?为什么我们不能简单地加载历史记录,然后从中获取交易信息?
问题在于 EA 交易工作期间,每个 EA 单独请求历史记录,因此如果 EA 交易在不同的时间运行,则历史记录的深度是不同的。这意味着当一个 EA 交易进入循环并针对其周期加载历史记录时,则在到达循环结束之前,它可能发现此历史记录已经被另一 EA 交易的请求所加载,因此需要进行身份验证。
顺便说一句,它可能不是最好的验证,但确实有用。因此,让我们继续。在循环中,我们调用类方法,该方法将交易的单证值输入到特别准备的缓存中。在调用 prHistory_Deals () 函数之后,我们再次加载历史记录。
通过这种方式安排如何验证交易历史记录在 prHistory_Deals () 函数工作期间是否出现任何变化。如果没有变化,则变量 History_Total_Deals 将等于 HistoryDealsTotal (),并且退出循环,从而只遍历一次。如果出现变化,则系统将启动第二次循环,并且继续重复,直到加载的单证的历史记录没有任何错误(不要忘记在末尾加上 ";"):
while(History_Total_Deals!=HistoryDealsTotal());
接下来,在 for 循环中进行虚拟仓位的计算。
如果交易成功地通过一系列的筛选(交易时间和交易幻数),则其量将增加虚拟仓位的部分,类型为交易所属的类型。
我想指出,我仅记录从 EA 交易启动时的虚拟仓位的计算,尽管也有其他选择。
在这里,应该指出如何精确计算仓位。依据从远古到现在我们大家都使用过的记录本,我们有支出和盈利,这些值之间的差值作为余额来统计,相同的方案也适用于仓位的计算:如果您建立 0.2 手卖出和 0.3 手买入的仓位,则表示您持有 0.1 手的买仓。建仓时间和价格差异属于盈利类别,但是您将持有的仓位是 0.1 手,类型为买入。
这是为什么我们简单地将 EA 交易进行的所有交易,按买入和卖出分别汇总,然后比较它们,从而获得总仓位的原因(实际上,这是验证函数余下部分进行的工作)。
计算持仓量:
volume=NormalizeDouble(volume_BUY-volume_SELL,DIGITS);
用结构中值的输出确定仓位的类型:
if(volume<0)pvm.type=POSITION_TYPE_SELL; else { if(volume>0)pvm.type=POSITION_TYPE_BUY; }
将持仓量输出到结构:
pvm.volume=fabs(volume);
函数值的输出:如果持仓量为 0,则其为 false,如果存在持仓量,则其为 true:
if(pvm.volume==0)return(false); else return(true);
现在,在具备虚拟仓位函数之后,我们可以轻松地草拟不与其“邻居”冲突的 EA 交易的代码。
为了节省空间,我将提供在前文中未出现的部分代码,而不是整个代码本身。
//+------------------------------------------------------------------ //| EA 代码 | //+------------------------------------------------------------------ //--- 输入参数 input ulong magic =1; // 标识号 input int SL =300; // 止损 input int TP =1000; // 获利 input int MA_Period =25; // MA 周期 input double lot =0.1; // 仓位持仓量 input int MA_shift =0; // 指标偏移 input ENUM_MA_METHOD MA_smooth =MODE_SMA; // 平滑类型 input ENUM_APPLIED_PRICE price =PRICE_OPEN; // 价格类型 //--- 我们将保存指标句柄 int MA_handle,type_MA,rezult; double v[2]; datetime CurrentTime; // 保存 EA 开始时间的变量 MqlTradeResult res; // OrderSend 响应结构的指针 MqlTick tick; // 最后市场信息结构的指针 CPositionVirtualMagic cpvm; CProvision prov; //+------------------------------------------------------------------ //| EA 初始化函数 | //+------------------------------------------------------------------ int OnInit() { CurrentTime=TimeCurrent();// 保存 EA 开始时间的变量 //--- 创建指标的句柄 MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,price); return(0); } //+------------------------------------------------------------------ //| EA 即时价格处理函数 | //+------------------------------------------------------------------ void OnTick() { if(CopyBuffer(MA_handle,0,0,2,v)<=0) {Print("#",magic,"复制错误");return;} type_MA=prov.TYPE(v); // 依据 MA 指示判断类型 if(cpvm.PositionVirtualMagic(magic,_Symbol,CurrentTime))// 如果此时有开仓 { if((int)cpvm.cTYPE()!=type_MA)// 检查是否是平仓时机 { Print("#",magic,"标识号对应的仓位持仓量 ",cpvm.cVOLUME(), " 反向仓位类型 ",(int)cpvm.cTYPE()," 依据 ",type_MA); //cpvm.cVOLUME() - 虚拟仓位持仓量 rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,cpvm.cVOLUME()+lot);// 反向仓位 if(rezult!=-1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume); else{Print("#",magic,"错误",GetLastError()); return;} } } else // 如果没有仓位则开仓 { Print("#",magic,"标识号对应的仓位持仓量 ",cpvm.cVOLUME()," 仓位类型 ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot);// 开仓 if(rezult!=-1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume); else{Print("#",magic,"错误",GetLastError()); return;} } }
在一个工具上运行 EA 交易三次,但是每次指定不同的时间框架和不同的幻数:
图 3. 让我们将不同的幻数指定到两个相同的 EA 交易(一个工具,不同的时间框架),启动第一个 EA 交易
图 4. 让我们将不同的幻数指定到两个相同的 EA 交易(一个工具,不同的时间框架),启动第二个 EA 交易
图 5. 结果是处理一个工具、具有不同幻数的 EA 交易在工作时不会冲突
试运行成功通过,则 EA 交易相互让路,不存在冲突。
技术规范的第一点已经实施完毕,但还有更多要实施的内容。
对幻数编码
要实施后续部分,我们需要开发一类方法,这些方法对信息进行编码/解码,从内置函数获取值,将值转换为指定的格式。
为此,重复一下编码要求(也可以说是开发技术规范):
- 方法需要对 EA 交易的名称进行编码(让我们将其称为数字名称)
- 识别“您的/外来的”代码(让我们将其称为互动代码)
- 交易品种代码,EA 交易处理的交易品种(为了能够从交易确定 EA 交易是从哪里工作的)
要开始,让我们选择新类的名称 - 将其称为 magic (一般名称),指定我们的枚举,使代码更容易被理解。
enum Emagic { ENUM_DIGITAL_NAME, // EA 数字名称 ENUM_CODE_INTERACTION,// 交互代码 ENUM_EXPERT_SYMBOL // EA 启动时的交易品种 };
枚举的工作很简单:您说明用逗号分隔的名称,编辑器按顺序向它们指定数字。
首先,如果您指定不同类型的变量(不适用于数字),则在从枚举指定参数时,在编译期间您将收到错误;其次,您获得澄清:您不仅仅指定 0,还发布指定 ENUM_DIGITAL_NAME 的命令。
对于结构或类的创建,我为枚举选择一个简单的名称。我简单地向一般选择的名称添加 E,得到 Emagic,相应地,对应的结构将为 Smagic,对应的类为 Cmagic。
再一次请注意,这一点并不是强制的,您可以将枚举称为 Enumerator,将结构称为 Structurer,将类称为 Classifier。但是这在名称上将不会提供共同性,阅读此类代码会不舒服。
接下来,让我们创建一个用于存储我们的代码的结构。
struct Smagic { ulong magicnumber; // 幻数是一种组装形式 - 它依顺序写 int digital_name; // 数字名 int code_interaction; // 交互代码 int expert_symbol; // EA 启动时的交易品种 };
之后,声明 Cmagic 类,在这个类中,我们注册对 Magic 进行编码和解码的所有方法,包括来自以前的 EA 交易的方法(简单地在当前类中声明它们并重写头部)
class Cmagic { protected: Smagic mag; SPositionVirtualMagic pvm; public: // 函数返回组装的幻数, 依输入数据组装 ulong SetMagic_request(int digital_name=0,int code_interaction=0); // 函数获取组装的幻数并按组装逻辑拆分 ulong SetMagic_result(ulong magicnumber); // 函数获取返回识别并返回组装幻数的请求部分 ulong GetMagic_result(Emagic enum_); // 函数获取返回的标识符 并返回组装幻数的请求部分文本解释 string sGetMagic_result(Emagic enum_); // 返回 EA 的虚拟仓位持仓量 double cVOLUME(){return(pvm.volume);} // 返回 EA 的虚拟仓位类型 ENUM_POSITION_TYPE cTYPE(){return(pvm.type);} // 虚拟仓位计算方法, 返回虚拟仓位存在或不存在 bool PositionVirtualMagic(Emagic enum_, string symbol, datetime CurrentTime); private: // 函数拆分幻数为三部分 并返回指定类别的部分 int decodeMagic_result(int category); // 金融工具交易品种的解释器对应数字代码 int symbolexpert(); // 数字代码的解释器对应的预描述文本 (EA) string expertcode(int code); // 数字代码的解释器对应的预描述文本 (交互) string codeinterdescript(int code); // 数字代码的解释器对应的金融工具交易品种 string symbolexpert(int code); // 记录单号周期至缓存区 void prHistory_Deals(ulong &buf[],int HTD); };
现在,我们将开发方法。
类中的第一个方法:
//+------------------------------------------------------------------ //| 函数返回组装的幻数, 依输入数据组装 | //+------------------------------------------------------------------ ulong Cmagic::SetMagic_request(int digital_name=0,int code_interaction=0) { if(digital_name>=1000)Print("指定 EA 数字名称不正确 (超过 1000)"); if(code_interaction>=1000)Print("指定识别代码不正确 (超过 1000)"); mag.digital_name =digital_name; mag.code_interaction =code_interaction; mag.expert_symbol =symbolexpert(); mag.magicnumber =mag.digital_name*(int)pow(1000,2)+ mag.code_interaction*(int)pow(1000,1)+ mag.expert_symbol; return(mag.magicnumber); }
此方法接收两个值:EA 交易的数字名称和互动代码。
ulong Cmagic::SetMagic_request(int digital_name=0,int code_interaction=0)
并且立即验证它们是否正确:
if(digital_name>=1000)Print("指定 EA 数字名称不正确 (超过 1000)"); if(code_interaction>=1000)Print("指定识别代码不正确 (超过 1000)");
但是没有对用户操作的反应,即使出现错误,它也仅是继续工作。
接下来是将用户指定的输入数据赋予结构,但是未指定交易品种,而是从私有方法获取:
int Cmagic::symbolexpert()
我将不提供其代码,因为它很长,会在附带的文件中提供。让我说此方法基本上是一张表,向 "market view"(市场观察)窗口中的每一个交易品种赋予一个对应的数字:例如,对于 EURUSD,它为 1,等等。
当然,您也可以通过编写一段代码来调查 "market view"(市场观察)窗口中有哪些货币,以动态方式获取此数据,但是此解决方案必须对应问题的复杂性,并且没有必要处理窗口的调用,因此我们以简单的方式来进行:构建货币列表,并向每种货币分配一个索引。
最后,整个方法中最关键的一行:
mag.magicnumber =mag.digital_name*(int)pow(1000,2)+ mag.code_interaction*(int)pow(1000,1)+ mag.expert_symbol;
从整个 Magic 完全不同的部分组合而成。这是将分配到我们的 EA 交易顺序的 Magic。
类的下一公共方法:
//+------------------------------------------------------------------ //| 函数获取组装的幻数 | //| 并按组装逻辑拆分 | //+------------------------------------------------------------------ ulong Cmagic::SetMagic_result(ulong magicnumber) { mag.magicnumber =magicnumber; mag.expert_symbol =decodeMagic_result(1); mag.code_interaction =decodeMagic_result(2); mag.digital_name =decodeMagic_result(3); return(mag.magicnumber); }
实际上,此方法相当于一个外壳,在整个结构中分配一个私有方法的三次调用的结果。在此说明符下进行声明是非常好的方法,因为在您调用类变量时它们不出现在弹出提示消息中,给人以所有工作都是通过一个公共函数进行的印象。
但是让我们返回到我们的私有函数:
//+------------------------------------------------------------------ //| 函数拆分魔幻号为三部分 | //| 并返回指定类别的部分 | //+------------------------------------------------------------------ int Cmagic::decodeMagic_result(int category) { string string_value=(string)mag.magicnumber; int rem=(int)MathMod(StringLen(string_value),3); if(rem!=0) { rem=3-rem; string srem="0"; if(rem==2)srem="00"; string_value=srem+string_value; } int start_pos=StringLen(string_value)-3*category; string value=StringSubstr(string_value,start_pos,3); return((int)StringToInteger(value)); }
可以看到,此方法可表示为从指定字段读取一个三位数字,举例而言,如果我们有一个幻数 123456789,则我们可以将其表示为 | 123 | 456 | 789 | ,如果指定字段为 1,则结果将为 789,因为字段是从右到左编号的。
因此,在调用的方法中使用所有三个字段之后,我们将所有获得的数据分配到结构。这通过将幻数转换为小字字体类型 string 的过程来进行:
string string_value=(string)mag.magicnumber;
接着是对单独的行组件进行排序。
接下来是两个类似的函数,在本质上它们是开关 switch,不同之处仅在于输出值的类型:
//+------------------------------------------------------------------ //| 函数获取返回的标识符 | //| 并返回组装幻数的请求部分 | //+------------------------------------------------------------------ ulong Cmagic::GetMagic_result(Emagic enum_) { switch(enum_) { case ENUM_DIGITAL_NAME : return(mag.digital_name); break; case ENUM_CODE_INTERACTION : return(mag.code_interaction); break; case ENUM_EXPERT_SYMBOL : return(mag.expert_symbol); break; default: return(mag.magicnumber); break; } } //+------------------------------------------------------------------------------+ //| 函数获取返回的标识符并返回 | //| 组装幻数的请求部分文本解释 | //+------------------------------------------------------------------------------+ string Cmagic::sGetMagic_result(Emagic enum_) { switch(enum_) { case ENUM_DIGITAL_NAME : return(expertcode(mag.digital_name)); break; case ENUM_CODE_INTERACTION : return(codeinterdescript(mag.code_interaction)); break; case ENUM_EXPERT_SYMBOL : return(symbolexpert(mag.expert_symbol)); break; default: return((string)mag.magicnumber); break; } }
函数返回幻数部分,该部分指定类型为 Emagic 的参数,第一个提供 ulong 类型的结果,该结果用于计算,第二个提供类型为 string 的结果,该结果用于可视化。
在函数 GetMagic_result () 中,一切的组织都很简单,它将结构的值分配到分支 switch, 而 sGetMagic_result () 稍微复杂一些。每个分支 case 调用一个将结构的值转换为可视形式的表函数。因此,如果值 mag.expert_symbol = 1,则第一个函数将返回 1,第二个函数将返回 EURUSD。
我已经说明了在对信息进行编码/解码中表函数的优点,因此,我将仅仅提一下应依据实施没有表的方法的复杂性以及相对于编写表所需的时间的优点单独考虑每一种情形。如果编写状态表更加容易,则没有必要使事情复杂化。但是如果编写表需要大量的时间,显然,最佳选择应是程序性方法。为了节省空间,我将不提供表(可以在附带的文件中找到它们)。
基本上就是这样,我们的类开发完毕,但是我们在以前的 EA 交易的开发过程中还使用四个剩余的函数。
我简单地在一个新的类中重新声明它们,尤其是考虑到它们仅需要稍微修改。
现在是主要的方法:
bool Cmagic::PositionVirtualMagic(Emagic enum_, string symbol, datetime CurrentTime)
不仅仅是声明为类 Cmagic 的方法,还具有一组不同的参数。
代替幻数,现在它通过用其计算仓位的幻数的字段获取标识。此外,尽管在最后一个选项中存在交易品种,它也仅用于获取有关交易品种的手数增量的信息。现在,它在筛选条件中规定,并且可以按照与其他交易品种相同的基础参与仓位统计的筛选。
这向我们提供了什么?现在,我们可以同时筛选在不同工具上由同一 EA 交易进行的交易。通过这种方式,它们不会与其他运行在不同工具上的类似 EA 交易混淆。老实说,说明使用这个新的计算系统的所有不同方式非常困难。读者可以自行决定他需要如此复杂的系统是为了什么。我只是强烈建议您不要在能够简单编写程序的情况下使问题复杂化,并且在明显需要的时候不要害怕此类复杂化。
现在,因为已经设计好类,是时候用新的 EA 交易进行测试了:
//+------------------------------------------------------------------ //| EA 代码 | //+------------------------------------------------------------------ //--- 输入参数 input ulong digital_name_ =4; // EA 数字名称 input ulong code_interaction_ =1; // 交互代码 input Emagic _enum =0; // 幻数模式 input int SL =300; // 止损 input int TP =1000; // 获利 input int MA_Period =25; // MA 周期 input double lot =0.4; // 仓位持仓量 input int MA_shift =0; // 指标偏移 input ENUM_MA_METHOD MA_smooth =MODE_SMA; // 平滑类型 input ENUM_APPLIED_PRICE price =PRICE_OPEN; // 价格类型 //--- 我们将保存指标句柄 int MA_handle,type_MA,rezult; static ulong magic; double v[2]; datetime CurrentTime;// 保存 EA 开始时间的变量 MqlTradeResult res; // OrderSend 响应结构的指针 MqlTick tick; // 最后市场信息结构的指针 CProvision prov; Cmagic mg; //+------------------------------------------------------------------ //| EA 初始化函数 | //+------------------------------------------------------------------ int OnInit() { magic=mg.SetMagic_request(digital_name_,code_interaction_); // EA 印记 (幻数标识符), 此标识符声明作为全局变量 // 用于 int CProvision::SendOrder(ENUM_ORDER_TYPE type,double volume) CurrentTime=TimeCurrent();// 保存 EA 开始时间的变量 //--- 创建指标的句柄 MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,price); return(0); } //+------------------------------------------------------------------ //| EA 即时价格处理函数 | //+------------------------------------------------------------------ void OnTick() { if(CopyBuffer(MA_handle,0,0,2,v)<=0) {Print("#",magic,"复制错误");return;} type_MA=prov.TYPE(v); // 依据 MA 指示判断类型 mg.SetMagic_result(magic);// 将信息放于结构 if(mg.PositionVirtualMagic(_enum,_Symbol,CurrentTime))// 如果有开仓 { if((int)mg.cTYPE()!=type_MA)// 检查是否是平仓时机 { mg.SetMagic_result(magic);// 将信息放于结构 Print("#",mg.GetMagic_result(_enum),"标识号对应的仓位持仓量 ",mg.cVOLUME(), " 反向仓位类型 ",(int)mg.cTYPE()," 依据 ",type_MA); //cpvm.cVOLUME() - 虚拟仓位持仓量 rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,mg.cVOLUME()+lot);// 反向仓位 if(rezult!=-1)Print("№",magic," 操作结果代码 ",rezult," 交易量 ",res.volume); else{Print("№",magic,"错误",GetLastError()); return;} } } else // 如果没有仓位则开仓 { Print("#",magic,"标识号对应的仓位持仓量 ",mg.cVOLUME()," 仓位类型",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot);// 开仓 if(rezult!=-1)Print("#",magic," 操作结果代码 ",rezult," 交易量 ",res.volume); else{Print("#",magic,"错误",GetLastError()); return;} } }
如前文所述,此 EA 交易程序极其简单,仅仅是为了演示不同的能力而创建的,在一个工具上运行三次:
图 6. 三个在不同图形上具有不同幻数的 EA 交易程序的安装
图 7. 结果是三个具有不同幻数的 EA 交易进行无冲突的交易
如 EA 交易的消息输出所示,所有三个 EA 交易都成功启动,并且证明没有冲突。
总结
通过提供向交易操作指定魔术顺序的机会,MQL5 的创建者大大改善了 EA 交易编写者的生活。但是开发人员只能向您提供工具 - 您需要成为实际获得钻石的那个人。
祝您好运,再见。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/112
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




我灵机一动,想到如果有一个现成的 MQL5 函数就好了,它可以生成构成当前未结头寸的 交易列表。我的意思是净额。这样就可以随时知道当前仓位中存在哪些交易和使用哪些 Magic。现在,如果一个智能交易系统用一个魔法开仓,第二个智能交易系统用第二个魔法加仓,那么交易者就用手关闭了部分仓位,而且无法知道哪个魔法被咬掉了。
HistorySelectByPosition() 错误?
以防万一,如果有人想使用这个库(类),请看这里: https://www.mql5.com/ru/forum/171241。
我想使用它,但遇到了问题
很明显,它可以随时调整,但尽管如此
下午好!
今天我发现了一个有趣的故障。我将智能 交易系统切换到虚拟 仓位,在测试过程中发现历史仓位有两次输入。虚拟仓位的算法正常工作,但在这个地方,脚本却在一秒钟内输入了两次,根据日志判断,刚刚打开第一个仓位后还没有打开交易的历史记录!?一切都按照通常的算法进行,我只是选择了仪器上的当前位置,然后用它进行操作。结果是这样的 - 新 Tick - 将历史记录加载到TimeCurrent - 在 magik 上选择一笔交易 - 没有交易 - 开仓 - 新 Tick - 在 magik 上选择一笔交易 - 没有交易(尽管我们知道交易已经结束) - 开仓 - 新 Tick - 将历史记录加载到TimeCurrent - 在 magik 上选择一笔交易 - 有一笔双倍成交量的交易。我在其他交易中没有看到这种情况,您认为出现这种故障的原因是什么?