市场模拟(第 23 部分):仓位视图(一)
概述
大家好,欢迎阅读本系列关于如何创建回放/模拟系统的又一篇文章。
在上一篇文章“市场模拟(第 22 部分):SQL 入门(五)”中,我们已详细讲解了所有必要内容,以便您能够理解后续文章中的材料。我知道,我们在上一篇文章中讨论的整个主题在第一次阅读时并不容易理解。而且,我想你们中的许多人甚至未曾想象过在 MQL5 中能做到某些事情。然而,到目前为止所看到的一切只是冰山一角。这是因为我只谈到了关于套接字和 SQL 的一点内容。
在我看来,这段期间所获得的知识将在接下来的阶段对你有所帮助。从现在开始,我们将探讨一个更加复杂的话题。即便如此,亲爱的读者,我会尽力不给你带来困扰。同时,我真心希望激励你深入学习,并更深入地探究我接下来将要展示的内容。
深呼吸,做好准备,因为现在我们要一头扎进回放/模拟系统的核心内容。关于这个系统开发的展示内容已经接近尾声。换句话说,剩下的内容已经不多了。但由于理论部分的内容要复杂得多,我将尽可能简单明了地介绍。编程部分本身相当简单明了。但是,如果你不了解背后的理论,那么在将回放模拟系统改进或调整以适应不同于我将要展示的任务时,你就几乎没有任何可用于进一步改进或适配系统的实际依据。
我不希望你只是编译和使用我提供的代码。我希望你能学习、理解,并且如果可能的话,做出比这更好的东西。有不少内容我不会演示具体该怎么做。这是因为,这些内容涉及我在实际工作中使用的模型,用于构建,或者更准确地说,实现一套交易系统,即所谓的 EA,该系统能够随时间推移不断学习。此外,它还能形成自身独特的交易策略,近似于人类操作者在实际交易中的行为模式。
在之前的文章“市场模拟(第五部分):创建 C_Orders 类(二)”中,在处理回放/模拟系统的部分,我们创建了一个小型 EA,该 EA 能够与之前开发的 Chart Trade 程序进行交互。那已经是很久以前的事了。所以,如果您不记得代码长什么样了,让我们快速回顾一下最终的结果。让我们从这一点开始。从那时起直到现在开发的所有内容,很快都会派上用场。但是,让我们回忆一下之前是怎么做的。
回顾回放/模拟系统中已经开发的内容
这一点很重要,因为如果不了解已经开发的内容,你最终会返回到那些已经完全具备功能且无需审查的部分。即便如此,我仍然打算展示如何完成特定任务。但我们将另找时间讨论这一点。现在,让我们来看看哪些部分已经完全具备功能。
回放/模拟系统由多个独立的应用程序组成。然而,当这些应用程序组合在一起时,它们几乎可以让您像连接到模拟账户或真实账户一样与 MetaTrader 5 进行交互。这些元素中,我们可以特别指出 Mouse Pointer 和 Chart Trade。Mouse Pointer 允许我们在图表上创建分析图形或与图表元素进行交互,而 Chart Trade 则是一个指标,可以通过 EA 发送市价单或市场执行请求。这是因为,在使用 Mouse Pointer 和 Chart Trade(它也是一个指标)时,我们可以通过消息传递协议与图表上的 EA 进行通信。
这三个应用程序单独来看并不十分实用。但一旦它们出现在图表上,您就可以通过发送市价单轻松与 MetaTrader 5 进行交互。然而,当开仓或平仓时,这仍然不会显示在图表上。虽然这并不完全正确。我们仍可借助 MetaTrader 5 自带的可视化提示。但是,如果您想使用跨品种订单系统 — 即对当前显示的交易品种以外的交易品种下达订单 — 那么您将完全得不到足够的视觉指示。这是因为,在使用跨品种订单时,我们正在查看我们正在观察的交易品种的图表。然而,订单或交易请求是在另一个交易品种上下达的,而该交易品种的图表并未打开。此时,MetaTrader 5 内置的可视化提示机制便无法再为我们提供帮助。
在使用回放/模拟服务时,同样会出现这种情况。尽管我们可以(并且很快就会)实现一种在回放/模拟系统中可视化操作的方法,但目前我们还没有合适的方法来实现这一点。原因很简单:我们没有一个可视化指示系统,能够让我们与可能存在于该特定交易品种(无论是真实交易品种还是模拟交易品种,即我们通过其回放数据的交易品种)的订单或仓位进行交互。
因此,尽管回放/模拟系统已经能够执行其任务,但我们首先需要实现一种方式来可视化此交易工具可能存在的任何仓位或订单,并与之交互。这是因为,如果没有这种方式,创建一个订单系统来支持回放/模拟系统内的分析就毫无意义。我们将无法与此交易品种的现有订单或仓位进行交互。
因此,创建这一部分成为我们的首要任务。别担心:首先,我们将创建一个相当简单的系统。这个解决方案足够简单,能让我们在开发回放/模拟系统时控制整个流程。那么,让我们现在就开始吧。为此,让我们创建一个新主题。
创建一个简单的仓位指标
我们将从以下内容开始:由于我们的 Chart Trade 指标并不关注挂单,而只关注即时下单,因此我们将首先创建一个显示未平仓位的基础指标。所以,目前我们不会关注挂单。我们将只关注未平仓位以及如何在图表上显示它们。值得再次强调的是,这里的目标不是展示 MetaTrader 5 已经具备的功能。我们的想法是展示 MetaTrader 5 没有向我们展示的内容,尤其是在使用跨品种订单系统时。
在我们开始查看将要实现的代码之前,我想先明确一点,现阶段我们的目标只是实现一个允许显示价格线的功能。我目前并没有在开发一个可以用于生产环境的工具。也就是说,首先我们需要确保在只有一个未平仓位时,价格线能够显示出来。目前,我们不会把重点放在管理多个未平仓位上。因此,我请那些最不耐烦的读者再耐心等一会儿。这里的目标是向初学者或爱好者解释如何让事情运转起来。
首先,我们将创建一个新文件。你可能会认为它应该是一个 EA 文件,因为我们将处理可能存在于交易服务器上的仓位。但并非如此,我们不会创建 EA。让我们来创建一个指标。源代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. return INIT_SUCCEEDED; 11. } 12. //+------------------------------------------------------------------+ 13. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 14. { 15. return rates_total; 16. } 17. //+------------------------------------------------------------------+ 18. void OnDeinit(const int reason) 19. { 20. } 21. //+------------------------------------------------------------------+
源代码
看到这里,你可能会想:“你疯了吗?还是完全失去理智了?像这样的事情,指标根本派不上用场。”亲爱的读者,请冷静下来,稍微有点耐心。你从未见过某样东西,并不意味着它无法实现或不存在。虽然很少有人会推荐,但使用指标来完成这项任务确实有许多优势。然而,当你看到代码是如何构建的,你最终会明白为什么我们会使用指标,而不是使用 EA。正如我们一段时间以来所说的,关于回放/模拟系统的这一系列文章中的内容将变得越来越复杂。但只要有可能,我都会尽量让一切尽可能简单易懂。
使用指标的问题在于,如果我们代码实现不当,可能会彻底破坏图表的运行。这是因为 MetaTrader 5 将所有指标都置于同一个执行线程中。因此,如果一个指标冻结或暂时无响应,其所处的图表也会面临同样的问题。许多人有时会在图表上添加多个指标。虽然这看似可以接受,但并不推荐,因为它们都在同一个线程中运行。一个指标可能会损害其他指标,从而危及整个平台的性能。
然而,我们在此的想法正是将指标放置在图表上,并保持其原样。换句话说,它不会进行任何更新。即使你手动更改了止盈或止损价格,此时指标也不会显示这一变化。稍后,当我详细解释如何执行这一更新时,再具体说明。所以,我们不要操之过急。当前首先要解决的是把价格线显示出来,其他部分暂时先放一放,因为我们将以最简单、最易懂的方式实现这一点。我希望你学会这一点,以便你能为个人项目做出所需的更改,而不是简单地使用我教你的内容来创作,之后变得完全受限,不知道下一步该做什么。
非常好,现在我们要修改之前看到的这段代码,以便测试一些东西。我们的新代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #include <Market Replay\Auxiliar\C_Terminal.mqh> 09. //+------------------------------------------------------------------+ 10. C_Terminal *Terminal; 11. //+------------------------------------------------------------------+ 12. int OnInit() 13. { 14. Terminal = new C_Terminal(); 15. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 16. Print ( 17. "Position Ticket: ", PositionGetInteger(POSITION_TICKET), " ", 18. "Opening Price: ",PositionGetDouble(POSITION_PRICE_OPEN), " ", 19. "Stop Position: ", PositionGetDouble(POSITION_SL), " ", 20. "Take Position: ", PositionGetDouble(POSITION_TP) 21. ); 22. 23. return INIT_SUCCEEDED; 24. } 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 27. { 28. return rates_total; 29. } 30. //+------------------------------------------------------------------+ 31. void OnDeinit(const int reason) 32. { 33. delete Terminal; 34. } 35. //+------------------------------------------------------------------+
基本仓位指标的源代码
由于这段代码相当简单,我将快速讲解一遍,以便您理解其工作原理。我假设您仅通过查看代码无法理解其含义。在第 08 行,我们添加了一个回放/模拟系统中已存在的文件。自本系列上次更新以来,该文件未发生任何更改。在第 10 行,我们指示将通过指针访问头文件中的类。第 14 行,我们初始化类。在第 15 行,我们要求 MetaTrader 5 告知我们该交易品种是否有未平仓位。如果不存在,我们将通过报告错误来终止指标。
现在注意了!当第 15 行代码执行完毕并返回 true 时,与所请求交易工具的仓位相关的数据即可使用。在此之前,我们无法获得可靠的数据。注意这个细节。如果第 15 行执行成功,则第 16 行将运行。在该行中,我们将第 15 行调用所获得的仓位数据打印到终端。最后,在第 23 行,我们返回一个值,表明该指标已成功初始化。
除非出于某种原因从图表中移除该指标,否则它不会发生任何变化。当这种情况发生时,我们在第 33 行销毁该类并将分配的内存返回给系统。
一切都很简单。因此,终端将显示以下数据,从下图所示仓位开始:

指标执行完其任务后,您可以查看其运行结果,如下图所示。

请注意,我们已成功获取这些数据。与普遍看法相反,我们确实可以直接从指标中获取有关持仓或挂单的数据。我们不一定需要使用 EA。然而,我们能够获取有关持仓或挂单的数据并不意味着我们可以使用指标来发送或修改持仓或挂单。请不要混淆这些概念。负责发送或修改持仓或挂单的组件是 EA。不要尝试通过指标来实现这一点,因为这行不通。
好的,一切都很顺利,甚至可以说是非常出色,但我们距离真正需要实施的目标还有很远的距离。然而,正如许多人所说:
智者一言足矣。
因此,任何已经知道如何向图表添加对象的人,可能都会理解这个想法。但亲爱的读者,如果你还不太明白,也不要灰心。我们现在要进入下一步的实施阶段。下一步内容会稍微多一些。即便如此,我们仍将努力使开发过程至少变得有趣。这是因为从现在开始,我们将面临许多可能性。但是,我们不会直接跳到最终结果。我们会循序渐进,因为为了将编程工作量降至最低,有许多要点需要考虑。请记住:我们已经有了几个现成的应用程序,如 Chart Trade、鼠标指标和基本的 EA。
现在我们需要创建仓位指标。理想情况下,应该将这个应用程序与其他三个应用程序一起放在图表上。但就目前而言,我们不必为此担心。首先,让我们创建一些对象并放置在图表上,以便我们可以看到与 MetaTrader 5 原生支持跨品种订单时相同的信息。那么,让我们开始工作吧。
将对象放置在图表上
你可能会认为,使用 MQL5 直接在图表上放置对象要么非常困难,要么非常简单。从某种意义上说,一切都取决于我们想要如何工作以及你对功能的期望。然而,在 MetaTrader 5 终端中将对象放置在图表上并不难。实际上,无论是使用 MQL5 代码还是直接从 MetaTrader 5 中操作,都非常简单。在 MetaTrader 5 的菜单中,有一个“插入”项,其下有一个“对象”子菜单,通过这个子菜单,我们可以非常简单直接地将一些对象直接放置在图表上。您可以在下一张图片中看到这一点:

然而,这里存在一个问题。我不太确定我之前是否提到过这一点。总之,亲爱的读者,从现在开始,你应该明白我们不能随意行事。你不应该仅仅因为这会让图表看起来更美观,或者甚至能展示我们认为必要的数据或信息,就在图表中添加对象。这样做若不谨慎,将是一个巨大的错误。请不要误解我,因为有许多人甚至都没有意识到这一点。他们迟早会遇到一个严重的问题,这个问题会彻底破坏使用 MetaTrader 5 的体验。
然而,问题并不真正在于 MetaTrader 5,而在于那些在图表上创建对象却不受任何控制的各种应用程序。即使这些对象不会使图像过载,也不会干扰正常阅读图表,但它们可能会成为真正的噩梦。它就像一块卡在地里的石头:我们以为它只是一块小鹅卵石,但当我们试图把它拔出来时,却发现它实际上是一块巨大的岩石,比我们想象的要大得多。
你或许在想:“好吧,但这与我何干,或者我为什么要关心?”如果你这么想,那我无话可说,只能祝你好运。但如果你真的感兴趣,想从使用图表上对象的应用程序中获取最佳体验,那么这个话题可能会让你非常感兴趣。这个问题不难发现;恰恰相反,它非常明显。如果你真的想看看这个问题是如何产生的,只需使用我之前发布的代码就足够了。当时,我并不了解 MetaTrader 5 是如何处理某些情况的。但对我们即将要做的事情而言,了解 MetaTrader 5 如何处理其中的一些情况是极其重要的。
如果你在图表上添加一张图片,使其覆盖整个图形区域,那么结果就会是一张壁纸,可以作为快速识别屏幕上哪个交易品种的方式。此外,在图表上使用图片比使用单色背景看起来更有吸引力。简而言之,这完全取决于个人喜好;最坏的情况是,我们可能会后悔自己的选择。好的,这可以通过直接在图表上放置一个对象来实现,或者使用专门的应用程序来实现。第一种情况下,我们将无法控制某些事情。在第二种情况下,我们将完全掌控整个过程。
好的,关注我文章的人都知道,在之前的内容中,我们曾创建了一个名为 C_DrawImage 的类。它的目的是允许放置内容为位图图像的对象。至少目前是这样,因为我们还没有展示如何处理其他图像格式。但这不是我们这里感兴趣的内容;另一个事实才是。因此,使用我们之前展示的整套代码,我们可以创建以下文件,如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This indicator allows you to add" 04. #property description "a wallpaper to the chart." 05. #property version "1.00" 06. #property indicator_chart_window 07. #property indicator_plots 0 08. //+------------------------------------------------------------------+ 09. #include <Market Replay\Auxiliar\C_DrawImage.mqh> 10. //+------------------------------------------------------------------+ 11. input string user00 = "WallPaper_1"; //WallPaper 12. input uchar user01 = 60; //Transparency 13. //+------------------------------------------------------------------+ 14. C_DrawImage *Draw; 15. C_Terminal *Terminal; 16. //+------------------------------------------------------------------+ 17. int OnInit() 18. { 19. Terminal = new C_Terminal(); 20. if (!Terminal.IndicatorCheckPass("Indicator WallPaper")) return INIT_FAILED; 21. Draw = new C_DrawImage(Terminal, "WallPaper", 0, "WallPapers\\" + user00 + ".bmp", NULL); 22. 23. return INIT_SUCCEEDED; 24. } 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 27. { 28. return rates_total; 29. } 30. //+------------------------------------------------------------------+ 31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 32. { 33. switch (id) 34. { 35. case CHARTEVENT_CHART_CHANGE: 36. (*Terminal).DispatchMessage(id, lparam, dparam, sparam); 37. (*Draw).Paint(0, 0, (*Terminal).GetInfoTerminal().Width, (*Terminal).GetInfoTerminal().Height, user01, 0); 38. break; 39. } 40. ChartRedraw((*Terminal).GetInfoTerminal().ID); 41. } 42. //+------------------------------------------------------------------+ 43. void OnDeinit(const int reason) 44. { 45. delete Draw; 46. delete Terminal; 47. } 48. //+------------------------------------------------------------------+
Wallpaper.mq5 的源代码
请注意,这只是一个指标。但这个文件实际上是做什么的呢?其实很简单。这个小巧且不起眼的文件实际上是我之前一篇文章中的另一个文件的更新版。正如我之前提到的,当时我对 MetaTrader 5 的某些方面并不了解。
这篇文章名为“如何让图表更有趣:添加背景”,面向那些对整体工作背后的动机和详细信息感兴趣的人。如果你对比一下代码,就会发现它们完全不同。确实如此,但差异程度可能并未你想象得那么大。那篇文章中的代码存在一个问题,而其解决方案极其简单。编译这段代码并将其作为 MetaTrader 5 应用程序加载到图表上后,图表上会出现一张背景图片。实际上,这张图片应该用作背景。
然而,虽然这在代码中很明显,但当时间周期改变时,实际情况却并非如此。即使我们告诉 MetaTrader 5 将 K 线置于前景,此壁纸仍然会覆盖它们,并可能完全隐藏图表。但情况甚至更糟。与我在这里展示的更新后的代码不同,最初创建的对象具有接收点击事件的默认优先级。
但问题到底是什么?如果图表上的对象很少且彼此孤立,那就没有问题。但如果我们放置一个覆盖整个图表的对象,比如壁纸,会发生什么呢?如果它与图表上其他对象具有相同的接收事件优先级,又会如何?情况开始变得有点复杂了,不是吗?这是因为 MetaTrader 5 不知道要将事件发送到哪个对象。
现在请思考以下情况:图表上已经有几个必要的对象。它们分别位于鼠标指标和 Chart Trade 指标中。但是,鼠标指标对象的优先级已降低至 -1。Chart Trade 指标的对象优先级为零。也就是说,即使我们尝试向在鼠标指标中创建的对象发送点击事件,如果鼠标指针位于 Chart Trade 面板的任何对象上,我们也无法接收到该事件。我们在 Chart Trade 面板的实现过程中已经看到了这一点。然而,亲爱的读者,我不知道你是否还记得这件事。
现在,一个新的复杂因素出现了。我们将在图表上创建并放置更多的对象。这是因为我们将要创建订单和仓位指标。然而,问题仍然相同。在订单和仓位指标中创建对象时应该怎么做?如果它们的事件接收优先级与 Chart Trade 面板相同,当一个对象与另一个对象重叠时,我们可能会遇到问题。
你可能会想:“但这很简单啊。前面的对象会优先。”是的,但我再重复一遍:我们说的是哪个对象?这是因为,如果两个对象以相同的优先级创建,并且都处于相同的位置,MetaTrader 5 会按照它们放置的顺序来绘制。换句话说,最后放置的对象会覆盖之前创建的对象。但这并非问题所在。问题在于,当发生需要重新绘制图表的事件时,比如我们改变时间周期时,会发生什么。此时,对象的创建顺序可能与之前不同,使得一切变得非常难以管理。如果所有对象都是由单个应用程序创建的,问题就能得到解决。然而,我们决定不将所有内容都集中在一个大文件中。因此,我们需要对对象的创建方式有更多的控制权。
最后的想法
如果有人原本以为事情会很快、很随意地推进,那么这篇文章大概会让他清醒一点。在此,我们开始概述在订单系统实现方面我们将遵循的方向。首先,我们需要创建某种指标以便与系统交互。其次,创建(或更确切地说是实现)绝不能混乱进行。如果发生这种情况,我们可能会完全失去对图表上现有对象的控制。第三,由于我们将实现的指标会在图表上添加更多对象,因此我们需要找到一些方法来避免可能的交互失败。因为如果两个对象位于同一位置,即使其中一个被覆盖,它也可能接收到事件,仅仅是因为它具有更高的优先级。
然而,在处理图表上的多个订单或仓位时,我不想遇到问题。这是因为,如果执行不当,我们可能会错误地关闭本想保持开放的仓位,而保持本想关闭的仓位。我想向你展示如何消除这种失误。因此,亲爱的读者,我希望你真正想学习,因为在回放/模拟系统完成之前,我们会看到许多新内容。
| 文件 | 描述 |
|---|---|
| Experts\Expert Advisor.mq5 | 演示 Chart Trade 与 EA 之间的交互(交互需要 Mouse Study)。 |
| Indicators\Chart Trade.mq5 | 创建一个窗口,用于配置要发送的订单(需要 Mouse Study 才能进行交互) |
| Indicators\Market Replay.mq5 | 创建用于与回放/模拟服务交互的控件(交互需要 Mouse Study)。 |
| Indicators\Mouse Study.mq5 | 提供图形控件与用户之间的交互(回放/模拟系统和真实市场都需要)。 |
| Indicators\Order Indicator.mq5 | 负责显示市价单,并允许用户与之互动和管理订单。 |
| Indicators\Position View.mq5 | 负责展示市场仓位,并允许与之互动和管理。 |
| Services\Market Replay.mq5 | 创建并维护市场回放/模拟服务(整个系统的主文件)。 |
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/13133
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
交易中的神经网络:不依赖特定数据的时间序列泛化(核心模型模块)
新手在交易中的10个基本错误
从基础到中级:对象事件(三)