MetaTrader 中的多机器人:从单图表中启动多个机器人
内容
概述
在金融市场领域,自动交易系统已成为决策过程中不可或缺的一部分。 这些系统可以配置为分析市场,制定入场和离场决策,并遵照预定义的规则和算法执行交易。 不过,在多个图表上设置和运行机器人可能是一项耗时的任务。 为每个图表单独配置每个机器人,这需要额外的工作量。
在本文中,我将向您展示我实现的一个简单模板,该模板允许您在 MetaTrader 4 和 5 中创建一个支持多图表的通用机器人。 我们的模板将允许您将机器人附加到一个图表,并在 EA 中处理其余图表。 因此,我们的模板大大简化了在多个图表上设置和运行机器人的过程,节省了交易者的时间和精力。 在本文中,我将详细研究在 MQL5 中创建此类机器人的过程,从构思到测试。
问题陈述和适用性限制
这个思路是不久前想到的,尽管我长期以来一直在观察专业卖家的类似决定。 换言之,我不是第一个也不是最后一位在这个领域提出思路的人,但与往常一样,程序员必须设定一些条件才能开始制定这样的决策。 在 MQL5 商店中开发此类智能系统的主要原因是满足用户对舒适度的渴望。 然而,就我而言,动机略有不同。 我的动机是,我首先必须同时测试若干金融产品的若干种策略,或者相同的策略,但为的是看看其多币种特征。
此外,在测试器中测试策略时,尤其是在多币种模式下,一个非常重要的因素是盈利能力的一般曲线,这是在依据历史数据进行回测时,针对自动交易系统进行任何评估的基础。 当依据一种金融产品单独测试交易系统时,后期很难合并这些报告。 我不知道是否有这样的工具,至少对于 MetaTrader 5 是这样。 至于终端的第四个版本,有一个非官方的工具能完成此类操作。 我至少在一篇文章中用过它,但当然这种方法并不可取。
除了测试过程之外,自动交易过程本身还是一个同样重要的环节,以及与独立工作的类似 EA 的同步,每个 EA 都运行在各自的图表上。 如果这样的图表太多,可能需要额外的计算机资源,减慢或恶化交易性能,并导致意外错误和其它令人不快的事件,可能对最终交易结果产生不利影响。 对于这样的每个 EA,我们需要提供唯一的订单 ID、针对高频服务器请求的保护,以及许多其它乍一看不明显的事情。
EA 的图形处理部分是一个单独且非常敏感的问题。 现在,所有熟练的 EA 创建者或多或少都至少制作了某些最小版本,指示 EA 已附加到图表上。 按这种方式,EA 看起来更严谨,激发更多的信心,最后,几乎始终如此,在图表上向用户显示一些信息有时可以更有效地控制 EA 交易过程。 此外,如有必要,可以添加手工控制的元素。 所有这些都称为用户界面。 在通过图表分发此类 EA 时,更新界面中的图形、文本和数字信息的负载呈指数级增长。 当然,当使用多模板时,我们有一个界面需要来自终端的最少资源。
当然,这样的模板并不能解决所有问题,但尽管如此,它对我的项目有很大帮助。 我使用不同的机器人,一般来说,所有方法都有存在的权利,但我认为许多萌新程序员可能会发现这种模式很有用。 没有必要完全复制它,但如果您愿意,您可以轻松调整它,以便满足您的需求。 我的目标不是给一些超凡的东西,而是展示和解释解决此类问题的选择之一。
MetaTrader 4 和 MetaTrader 5 终端在使用多机器人方面的区别
我喜欢最新的 MetaTrader 5,就是其测试器的强大,它为您提供了同时在多种金融产品上测试所需的所有功能,前提是您使用上述 EA 开发方法。 测试仪按时间自动同步报价,为您提供时间尺度上清晰同步的盈利能力曲线。 MetaTrader 4 还没有这样的功能。 我认为,这是它最大的缺点。 尽管如此,值得注意的是,MetaQuotes 正在尽最大努力支持第四版终端,因为其受欢迎程度仍然很高。 作为 MetaTrader 4 的活跃用户,我可以说这些缺点并不像看起来那么严重。
MQL4 语言最近已更新为 MQL5。 这意味着在编写类似像我们这样的模板时,我们在代码中具有的差异最小。 尝试为两个终端实现一些东西是我的好传统,如此您就会得到适合两个终端的模板。 对旧终端的这种改进,除其他外,令我们能够满足我们真正需要的以下功能:
- CopyClose - 请求柱线收盘价
- CopyOpen - 请求柱线开盘价
- CopyHigh - 请求柱线峰值
- CopyLow - 请求柱线最低价
- CopyTime - 请求柱线开盘时间
- SymbolInfoTick - 请求所请求品种的最后一个传入跳价
- SymbolInfoInteger - 请求品种数据,可以用整数和编号列表来描述
- SymbolInfo******* - 我们需要的其它功能
这些功能在 MQL4 和 MQL5 中都存在。 这些函数允许您获取任何品种和周期的柱线数据。 因此,第四版和第五版测试器之间唯一令人不快的区别是,第四版终端中的这些功能仅适用于正在执行测试的当前图表,其余请求则简单地通知您由于 MetaTrader 4 测试器的特殊性,没有数据。 因此,在测试我们的模板时,您只能获得所选品种的交易,并且只有单个机器人的一条盈利曲线。
在第五版终端中,您会得到所有请求品种盈利的共同曲线。 至于在交易中应用,当在两个终端中直接使用这样的机器人进行交易时,您将获得这种模板的全部性能。 换言之,区别仅在于测试器。 但即使在这种情况下,您也可以摆脱这样一个事实,即在创建 EA 时,最好从 MetaTrader 5 版本开始。 完成所有必要的测试后,您就可以迅速制作 MetaTrader 4 的版本。
当然,有许多差异我没有涉及。 我只想强调其中一些的重要性,因为在为这样的模板构建复杂的结构时,必须明白这些细微差别。 MetaTrader 5 绝对比它的前身更好,但我不想摆脱第四版终端,因为在许多情况下,与第五版终端相比,它对计算资源的需求并没有那么大。 这两种工具都很不错。
构建通用模板的细微差别
为了构建这样的模板,您应该了解终端的工作原理、什么是智能交易系统,以及什么是 MetaTrader 图表。 此外,您应该了解每个图表都是一个单独的对象。 每个这样的图表可以与多个指标相关联,并且只能与一个 EA 相关联。 也许有若干个雷同的图表。 通常制作若干个图表是为了在一个品种周期上运行多个不同的 EA,或者运行一个具有不同设置的 EA 的多个副本。 了解这些微妙之处,我们就能得出结论,从而摒弃多个图表而支持我们的模板,我们必须在模板中实现所有这些。 这可以用图表的形式表示:
单论的话,它应该说的是跳价。 这种方式的缺点是,我们将无法订阅处理程序,以便处理每个图表出现的新跳价。 我们将不得不应用机器人正在操作的图表中的跳价,或使用计时器。 终极情况,对于跳价机器人,这意味着令人不快,具体如下:
- 我们将不得不编写自定义的 OnTick 处理程序
- 这些处理程序必须作为 OnTimer 的衍生品实现
- 跳价不会是完美的,因为 OnTimer 工作时会有延迟(延迟程度并不重要,但重要的是它存在)
- 若要获取跳价,您需要 SymbolInfoTick 函数
我认为对于那些每毫秒都要计数的人来说,这可能是一个极具诱惑的时刻,特别是对于那些喜欢套利的人来说。 然而,我没有在我的模板中强调这一点。 在构建不同系统的这些年里,我遇到了柱线交易范式。 这意味着交易操作和其它计算大部分发生在新柱线出现那一刻。 这种方式有许多明显的优势:
- 即使判定新柱线开始的不准确,也不会显著影响交易
- 柱线的周期越长,这种影响就越小
- 柱线形式的离散化可将测试速度提高几个数量级
- 基于真实跳价和人工报价上进行测试时,测试品质相同
该方式教授构建 EA 的某种范式。 这种范式消除了与跳价 EA 关联的许多问题,加快了测试过程,提供了更高的数学盈利期望,这是主要障碍,并且还节省了大量时间和算力。 我认为,我们可以发现更多优势,但我认为在本文中这些内容已经足够了。
若要实现我们的模板,没必要在我们的模板中实现交易终端工作区的整个结构,为每个机器人实现一个单独的图表足矣。 这不是最佳结构,但如果我们认可每个单独的金融产品在列表中只存在一次,那么就不需要这种优化。 它将看起来像这样:
我们已经实现了最简单的图表实现结构。 现在是时候思考这种模板的输入了,更重要的是,在 MQL5 语言允许的可能范围内,如何考虑每种状况下图表和 EA 的动态数量。 解决此问题的唯一途径就是使用字符串输入变量。 字符串允许我们存储非常大量的数据。 事实上,为了描述这样一个模板的所有必要参数,我们将需要在输入数据中用到动态数组。 当然,没人会仅仅因为这样的机会只有少量人用到而实现这些事情。 字符串是我们的动态数组,我们可以在其中放置任何我们想要的东西。 那么,我们就用它好了。 对于我最简单的模板,我决定引入三个变量,如下所示:
- Charts - 我们的图表 (列表)
- Chart Lots - 交易手数 (列表)
- Chart Timeframes - 图表周期 (列表)
通常,我们可以将所有这些数据组合成一个字符串,但是它的结构会很复杂,潜在用户很难弄清楚如何正确描述数据。 此外,在填写时也很容易出错,并且在用到它时,我们会遇到很多非常不愉快的事情,更不用说从字符串中提取这些数据的转换函数的复杂性令人难以置信。 我曾在一些卖家中看到了类似的解决方案,总的来说,他们做的一切都是正确的。 所有数据简单地排列,并以逗号分隔。 在 EA 伊始,调用特殊函数从字符串中提取出这些数据,并填充相应的动态数组,然后就可在代码中使用。 我们也将遵循这条道路。 我们可以添加更多具有相同枚举规则的相似字符串。 我决定采用 ":" 作为分隔符。 如果我们使用逗号,那么就不清楚如何处理双精度数组,例如图表手数。 可以添加更多这样的字符串变量,并且通常可以构建更完整和通用的模板,但我在这里的任务只是展示如何实现这一点,并为您提供可以快速轻松地修改模板的第一个版本。
仅仅实现这样的数组是不够的,还需要实现公共变量,例如:
- Work Timeframe For Unsigned - 未指定的图表周期
- Fix Lot For Unsigned - 未指定的手数
应填写 Charts 列表。 对于 Chart Lots 和 Chart Timeframes,可选相同的操作。 例如,我们可以针对所有图表取单手,针对所有图表采用相同的周期。 类似的功能将在我们的模板中实现。 最好尽可能应用如此这般实现的规则,从而确保在设置基于该模板构建的 EA 的输入参数时更简洁明了。
现在,针对这种模式的最小实现,我们来定义少量更为重要的变量:
- Last Bars Count - 我们为每个图表存储最后柱线计数
- Deposit For Lot - 指定手数对应的资金
- First Magic - 单独 EA 的独有成交 ID
我认为,第一个变量非常清楚。 第二个变量有点难以领会。 这就是我如何在 EA 中调校自动手数的。 如果我将其设置为 “0”,那么我通知算法它只需要交易固定手数,该数字已在相应字符串或我们上面研究的共享变量中指定。 否则,我设置了所需的资金,如此便可以应用设置中指定的手数。 这很容易理解,对于较小或较大的资金,该手数的数值会根据等式变化:
- Lot = Input Lot * ( Current Deposit / Deposit For Lot )
我认为现在一切都应该清楚了。 如果我们想要一个固定手数,我们将该项设置为零,在其它情况下,我们将在输入设置中的资金调整为基于风险。 我认为,它既廉价又令人放心。 如有必要,您可以更改自动手数的风险评估方法,但我个人喜欢这个选项,想太多其实并无意义。
值得一提的是同步,特别是关于设置智能系统魔幻数字等问题。 当交易是依靠 EA,甚至以混合形式进行时,有自尊心的那些程序员都会特别在意这个特殊的变量。 问题是,当运行多个 EA 时,尤为重要的是要确保这样的每个 EA 都应有自己唯一的 ID。 否则,在处理订单、成交或持仓时,您会一团糟,且您的策略将停止正常操作,在大多数情况下,它们将彻底停工。 我希望我不必解释为什么。 每次将 EA 放置在图表上时,我们都需要配置这些 ID,并确保它们不会重复。 即使是单纯的失误也可能导致灾难性的后果。 此外,如果您不小心关闭了运行 EA 的图表,则必须重新配置它。 结果就是,出错的可能性大大增加。 此外,它在许多其它方面都非常令人不满。 例如,您关闭图表了,并忘记了其所用的 ID。 在这种情况下,您就不得不挖掘交易历史来寻找它。 如果缺了 ID,新重新启动的 EA 也许操作不正常,并可能会发生更多不愉快的事情。
采用像我这样的模板,令我们摆脱了这种控制,并把可能出错的情况降至最小,因为我们只需要在 EA 设置中设置起始 ID,而其余的 ID 将使用增量自动生成,并分配给相应的 EA 副本。 此过程将在每次重新启动时自动执行。 无论如何,只记住一个起始 ID,比记住中途的一些随机 ID 要容易得多。
编写通用模板
到了实现模板的时候了。 我将尝试省略多余的元素,因此每个需要此模板的人都可以下载,并在源代码中查看其余元素。 在此,我只展示与我们的思路直接相关的事物。 止损级别和其它参数由用户定义。 您可以在源代码中找到我的实现。首先,我们要定我们肯定需要的输入变量:
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes input int LastBars=10;//Last Bars Count input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned input double RepurchaseLotE=0.01;//Fix Lot For Unsigned input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix) input int MagicE=156;//First Magic
在此,您可以看到一个填充示例,以字符串变量形式反映我们的动态数组,就像共享变量的示例一样。 顺便说一下,这段代码在 MQL4 和 MQL5 中看起来是一样的。 我尝试令一切尽可能相似。
现在,我们决定如何提取字符串数据。 这将由相应的函数完成,但首先我们将创建数组,我们的函数将把从字符串中提取的数据添加到其中:
//+------------------------------------------------------------------+ //|Arrays | //+------------------------------------------------------------------+ string S[];// Symbols array double L[];//Lots array ENUM_TIMEFRAMES T[];//Timeframes array
以下函数填充这些数组:
//+------------------------------------------------------------------+ //| Fill arrays | //+------------------------------------------------------------------+ void ConstructArrays() { int SCount=1; for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools { if (SymbolsE[i] == ':') { SCount++; } } ArrayResize(S,SCount);//set the size of the character array ArrayResize(CN,SCount);//set the size of the array to use bars for each character int Hc=0;//found instrument index for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools { if (i == 0)//if we just started { int LastIndex=-1; for (int j = i; j < StringLen(SymbolsE); j++) { if (StringGetCharacter(SymbolsE,j) == ':') { LastIndex=j; break; } } if (LastIndex != -1)//if no separating colon was found { S[Hc]=StringSubstr(SymbolsE,i,LastIndex); Hc++; } else { S[Hc]=SymbolsE; Hc++; } } if (SymbolsE[i] == ':') { int LastIndex=-1; for (int j = i+1; j < StringLen(SymbolsE); j++) { if (StringGetCharacter(SymbolsE,j) == ':') { LastIndex=j; break; } } if (LastIndex != -1)//if no separating colon was found { S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1)); Hc++; } else { S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1)); Hc++; } } } for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars { CN[i]=LastBars; } ConstructLots(); ConstructTimeframe(); }
简而言之,感谢分隔符,此处计算字符串中的数据量。 基于第一个数组,所有其它数组的大小设置类似于含有品种的数组,首先填充品种之后,接着由函数 Construct Lots 和 ConstructTimeframe填充。 它们的实现与此函数的实现类似,但也存在一些差异。 您可以在源代码中看到它们的实现。 我并未把它们列到文章之中,以免显示重复的代码。
现在我们需要为虚拟图表和与之链接的虚拟机器人创建相应的类。 我们首先定义将存储在数组中的虚拟图表和 EA :
//+------------------------------------------------------------------+
//| Charts & experts pointers |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];
我们先从图表类开始:
//+------------------------------------------------------------------+ //| Chart class | //+------------------------------------------------------------------+ class Chart { public: datetime TimeI[]; double CloseI[]; double OpenI[]; double HighI[]; double LowI[]; string BasicSymbol;//the base instrument that was extracted from the substring double ChartPoint;//point size of the current chart double ChartAsk;//Ask double ChartBid;//Bid datetime tTimeI[];//auxiliary array to control the appearance of a new bar static int TCN;//tcn string CurrentSymbol;//symbol ENUM_TIMEFRAMES Timeframe;//timeframe int copied;//how much data is copied int lastcopied;//last amount of data copied datetime LastCloseTime;//last bar time MqlTick LastTick;//last tick fos this instrument Chart() { ArrayResize(tTimeI,2); } void ChartTick()//this chart tick { SymbolInfoTick(CurrentSymbol,LastTick); ArraySetAsSeries(tTimeI,false); copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI); ArraySetAsSeries(tTimeI,true); if ( copied == 2 && tTimeI[1] > LastCloseTime ) { ArraySetAsSeries(CloseI,false); ArraySetAsSeries(OpenI,false); ArraySetAsSeries(HighI,false); ArraySetAsSeries(LowI,false); ArraySetAsSeries(TimeI,false); lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI); lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI); lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI); lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI); lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI); ArraySetAsSeries(CloseI,true); ArraySetAsSeries(OpenI,true); ArraySetAsSeries(HighI,true); ArraySetAsSeries(LowI,true); ArraySetAsSeries(TimeI,true); LastCloseTime=tTimeI[1]; } ChartBid=LastTick.bid; ChartAsk=LastTick.ask; ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT); } }; int Chart::TCN = 0;
该类只有一个函数,即控制跳价和柱线的更新,以及识别特定图表的一些必要参数的必要字段。 那里缺少了一些参数。 如果需要,您可以通过添加其更新来补充所缺,例如,更新 ChartPoint。 柱线数组采用 MQL4 风格。 我仍然发现以 MQL4 中所用预定数组非常方便。 当您知道零号柱线对应当前柱线时,这非常方便。 无论如何,这只是我的愿景。 您可以随意遵循自己的方式。
现在我们需要描述一个单独的虚拟 EA 类:
//+------------------------------------------------------------------+ //| Bot instance class | //+------------------------------------------------------------------+ class BotInstance//expert advisor object { public: CPositionInfo m_position;// trade position object CTrade m_trade;// trading object ///-------------------this robot settings---------------------- int MagicF;//Magic string CurrentSymbol;//Symbol double CurrentLot;//Start Lot int chartindex;//Chart Index ///------------------------------------------------------------ ///constructor BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index { chartindex=chartindex0; MagicF=MagicE+index; CurrentSymbol=Charts[chartindex].CurrentSymbol; CurrentLot=L[index]; m_trade.SetExpertMagicNumber(MagicF); } /// void InstanceTick()//bot tick { if ( bNewBar() ) Trade(); } private: datetime Time0; bool bNewBar()//new bar { if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 ) { if (Time0 != 0) { Time0=Charts[chartindex].TimeI[1]; return true; } else { Time0=Charts[chartindex].TimeI[1]; return false; } } else return false; } //////************************************Main Logic******************************************************************** void Trade()//main trade function { //Close[0] --> Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart //Open[0] --> Charts[chartindex].OpenI[0] ----------------------------------------------------------------------- //High[0] --> Charts[chartindex].HighI[0] ----------------------------------------------------------------------- //Low[0] --> Charts[chartindex].LowI[0] ------------------------------------------------------------------------- //Time[0] --> Charts[chartindex].TimeI[0] ----------------------------------------------------------------------- if ( true ) { CloseBuyF(); //CloseSellF(); } if ( true ) { BuyF(); //SellF(); } } double OptimalLot()//optimal lot calculation { if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE); else return CurrentLot; } //here you can add functionality or variables if the trading function turns out to be too complicated //////******************************************************************************************************************* ///trade functions int OrdersG()//the number of open positions / orders of this virtual robot { ulong ticket; bool ord; int OrdersG=0; for ( int i=0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol ) { OrdersG++; } } return OrdersG; } /////////********/////////********//////////***********/////////trade function code block void BuyF()//buy market { double DtA; double CorrectedLot; DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime if ( (DtA > 0 || DtA < 0) ) { CorrectedLot=OptimalLot(Charts[chartindex]); if ( CorrectedLot > 0.0 ) { //try buy logic } } } void SellF()//sell market { //Same logic } void CloseSellF()//close sell position { ulong ticket; bool ord; for ( int i=0; i<PositionsTotal(); i++ ) { ticket=PositionGetTicket(i); ord=PositionSelectByTicket(ticket); if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol ) { //Close Sell logic } } } void CloseBuyF()//close buy position { //same logic } bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot { int MagicT[]; ArrayResize(MagicT,magiccount); for ( int i=0; i<magiccount; i++ ) { MagicT[i]=MagicE+i; } for ( int i=0; i<ArraySize(MagicT); i++ ) { if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true; } return false; } /////////********/////////********//////////***********/////////end trade function code block };
我删除了一些重复的逻辑,从减少了代码量。 就是这个类,实现了 EA 的整个算法。 该类中存在的主要功能:
- Trade() - 相应图表上柱线处理程序中调用的主交易函数
- BuyF() - 按市价买入函数
- SellF() - 按市价卖出函数
- CloseBuyF() - 按市价平仓多头持仓的函数
- CloseSellF() - 按市价平仓空头持仓的函数
这是演示按柱线交易的最小函数集。 对于此演示,我们只需要任何一笔开仓,并在下一根柱线上将其平仓。 这在本文的框架内就足够了。 该类中的一些附加功能能补充理解:
- OrdersG() - 计算链接到图表的特定交易品种上的持仓
- OptimalLot() - 在将手数发送到交易函数之前准备一个手数(选择固定手数或自动计算手数)
- bOurMagic() - 检查历史记录中的交易是否符合允许的交易列表(仅用于整理自定义历史记录)
可能需要这些函数来实现交易逻辑。 提醒新柱线出现的处理程序也是合理的:
- InstanceTick() - 在单独的 EA 实例上进行跳价模拟
- bNewBar() - 检查新柱线出现的宣示(在 InstanceTick 内部使用)
如果宣示新柱线出现,则触发 Trade 函数。 这是要设置主要交易逻辑的函数。 与相应图表的连接是利用创建实例时分配的 chartindex 变量执行的。 如此,每个 EA 实例都知道它所引用的图表。
现在,我们研究虚拟图表和 EA 本身的创建过程。 首先创建虚拟图表:
//+------------------------------------------------------------------+ //| Creation of graph objects | //+------------------------------------------------------------------+ void CreateCharts() { bool bAlready; int num=0; string TempSymbols[]; string Symbols[]; ConstructArrays();//array preparation int tempcnum=CN[0]; Chart::TCN=tempcnum;//required number of stored bars for all instruments for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph { Charts[j] = new Chart(); Charts[j].lastcopied=0; ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays ArrayResize(Charts[j].OpenI,tempcnum+2);//---------------------------------- ArrayResize(Charts[j].HighI,tempcnum+2);//---------------------------------- ArrayResize(Charts[j].LowI,tempcnum+2);//----------------------------------- ArrayResize(Charts[j].TimeI,tempcnum+2);//---------------------------------- Charts[j].CurrentSymbol = S[j];//symbol Charts[j].Timeframe = T[j];//timeframe } ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots }
创建图表,并使用虚拟 EA 设置数组大小后,我们需要创建 EA 本身的实例,并实现虚拟 EA 与图表的关联:
//+------------------------------------------------------------------+ //| create and hang all virtual robots on charts | //+------------------------------------------------------------------+ void CreateInstances() { for (int i = 0; i < ArraySize(S); i++) { for (int j = 0; j < ArraySize(Charts); j++) { if ( Charts[j].CurrentSymbol == S[i] ) { Bots[i] = new BotInstance(i,j); break; } } } }
创建虚拟 EA 时,在虚拟 EA 的每个实例中依据所设置的 “j” 索引进行连接。 上面显示的相应变量在此处高亮显示。 当然,所有这些都可以通过多种方式完成,而且要优雅得多,但我认为它主要是总体思路很清晰。
剩下的就是展示如何在每个图表和与之关联的 EA 上模拟跳价:
//+------------------------------------------------------------------+ //| All bcharts & all bots tick imitation | //+------------------------------------------------------------------+ void AllChartsTick() { for (int i = 0; i < ArraySize(Charts); i++) { Charts[i].ChartTick(); } } void AllBotsTick() { for (int i = 0; i < ArraySize(S); i++) { if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick(); } }
我唯一想要提请注意的是,这个模板是通过我重新设计过的更复杂的模板获得,该模板旨在用于更严肃的目的,故这里或那里可能有多余的元素。 我认为,如果需要,您可以轻松删除它们,并令代码更整洁。
除了模板之外,还有一个简单的界面,我认为它也可以派上用场,例如,在自由职业者版面,或出于其它目的而编写订单时:
我在此界面中留下了可用空间,足以容纳三个条目,以便应对您没有足够空间的情况。 如有必要,您可以轻松扩展或完全更改其结构。 如果我们想在这个特定示例中添加三个缺失的字段,我们需要在代码中找到以下位置:
//+------------------------------------------------------------------+ //| Reserved elements | //+------------------------------------------------------------------+ "template-UNSIGNED1",//UNSIGNED1 "template-UNSIGNED2",//UNSIGNED2 "template-UNSIGNED3",//UNSIGNED3 //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1 //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2 //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3 //////////////////////////// //TempText="UNSIGNED1 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText); //TempText="UNSIGNED2 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText); //TempText="UNSIGNED3 : "; //TempText+=DoubleToString(NormalizeDouble(0.0),3); //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText); ///////////////////////////
前三项在界面上分配新元素的名称,后三项在 EA 开始时创建界面时用到,而最后三项在函数中更新界面上的信息。 现在是时候测试这两个模板的性能了。 测试仪可视化工具足以进行视觉演示。 我只显示 MetaTrader 5 的选项,因为它的可视化工具要强很多。 此外,操作结果将清楚地显示效率确认所需的一切:
如您所见,我们已经上传了主要外汇对的所有七个图表。 可视化日志显示,所有列出品种都在进行交易。 交易按照需求独立执行。 换言之,EA 在各自的图表上进行交易,根本不混杂。
结束语
在本文中,我们回顾了为 MetaTrader 4 和 MetaTrader 5 终端构建通用模板的主要细微差别,制作了一个简单但有效的模板,分析了其操作中的要点,并利用 MetaTrader 5 测试器可视化工具确认了其可行性。 我认为,现在很明显,如这般的模板并不太复杂。 通常,您可以对此类模板进行各种实现,但很明显,这样的模板可以完全不同,而仍然适用。 最主要的是了解构建这种结构的基本细微差别。 如有必要,您可以重新设计模板,以供个人所用。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/12434