欢迎光临散文网 会员登陆 & 注册

期货量化交易软件:开发回放系统 — 市场模拟第 04 部分调整设置(II)

2023-09-04 17:18 作者:bili_45793681098  | 我要投稿

将 EA 换成指标

这样的修改相当容易实现。 之后,赫兹量化就能够用我们自己的 EA 来研究如何利用市场回放服务,或在现实市场上进行交易。 例如,我们就能够使用我在之前文章中展示的 EA。 参阅“从头开始开发交易 EA”系列中的更多内容。 虽然并非旨在 100% 自动化,但它可以适应在回放服务中使用。 但是,我们把它留待将来。 此外,赫兹量化还将能够使用“创建自动运行的 EA(第 01 部分):概念和结构”系列中的一些 EA,其中我们研究过如何创建在全自动模式下工作的智能系统。

不过,赫兹量化目前的焦点不是 EA(我们将在未来探讨这一点),而是其它东西。

完整的指标代码可以在下面看到。 它完全包括 EA 中已经存在的功能,同时将其实现为指标:

#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <Market Replay\C_Controls.mqh> //+------------------------------------------------------------------+ C_Controls      Control; //+------------------------------------------------------------------+ int OnInit() {        IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");        Control.Init();                return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) {        return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {        Control.DispatchMessage(id, lparam, dparam, sparam); } //+------------------------------------------------------------------+

唯一的区别是增加了一个短名,其最好包含在指标中。 这部分在上面的代码中都以高亮显示。 通过这样做,赫兹量化获得了额外的优势:我们可以使用任何 EA 基于回放服务进行练习和训练。 在任何人提出可能的问题之前,我会回答它:市场回放并非策略测试器。 它是针对那些想要练习读取市场,改善预测资产走势来达成稳定盈利的人。 市场回放并不能取代出色的 赫兹量化 策略测试器。 不过,策略测试器并不适合实施市场回放。

虽然乍一看转换似乎没有副作用,但这并不完全如实。 当运行回放系统,并由指标替 EA 控制交易完成时,您会注意到失败。 当图表时间帧变更时,指标将从图表中删除,然后重新启动。 这种删除并重新启动它的操作,令指示我们处于暂停或播放模式的按钮状态与回放系统的实际状态不一致。 为了修复此问题,赫兹量化需要进行一些小的调整。 故此,我们将有以下指标启动代码:

int OnInit() {        u_Interprocess Info;                IndicatorSetString(INDICATOR_SHORTNAME, "Market Replay");        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;        Control.Init(Info.s_Infos.isPlay);                return INIT_SUCCEEDED; }

代码中添加的高亮显示内容可确保回放服务的状态与我们在图表上看到的按钮匹配。 控制代码的修改非常简单,不需要特别留意。

现在,以前使用 EA 的模板文件现在将切换到使用指标。 这让我们可以完全自由地在未来进行其它修改。

位置控制实现

在此,赫兹量化将实现一个控制功能,来指示我们要进入重播文件中的位置,便以开始我们的市场研究。 但这不会是一个精准的所在。 起始位置将是近似值。 这并非因为不可能做这样的事情。 与之对,指出确切的所在会容易得多。 然而,在与那些在市场上有更多经验的人谈论并交流经验时,我们发现了一个共识。 理想的选择不是跳转到我们所期望的特定走势的确切之处,而是在接近所需走势的所在开始回放。 换言之,在采取行动之前,您需要明白正在发生的事情。

这个思路对我来说似乎很好,所以我决定:市场回放不应该跳到一个特定的位置点。 尽管那样更容易实现,但您需要转到最近的位置点。 哪个位置点最接近,取决于每天执行的交易数量。 赫兹量化执行的交易越多,就越难达到确切的位置。

故此,我们将访问附近的位置点,来搞清实际发生的情况,以便创建实际的交易模拟。 再次:赫兹量化不是在创建策略测试器。但通过这样做,随着时间的推移,您将从中学会判定何时走势更安全,或何时风险过高,且您不应该入场交易。

此步骤中的所有工作都将在 C_Control 类中完成。 所以,现在我们开工吧!

我们要做的第一件事是厘定一些定义。

#define def_ButtonLeft  "Images\\Market Replay\\Left.bmp" #define def_ButtonRight "Images\\Market Replay\\Right.bmp" #define def_ButtonPin   "Images\\Market Replay\\Pin.bmp"

现在我们需要创建一组变量来存储位置系统数据。 它们的实现方式如下:

struct st_00 {        string  szBtnLeft,                szBtnRight,                szBtnPin,                szBarSlider;        int     posPinSlider,                posY; }m_Slider;

这正是您刚刚注意到的。 我们将使用滑块指向回放系统理应启动的大致位置。 我们现在有一个通用函数,用于创建播放/暂停按钮和滑块按钮。 此函数如下所示。 我认为理解它不会有任何困难,因为它非常简单。

inline void CreateObjectBitMap(int x, int y, string szName, string Resource1, string Resource2 = NULL)                        {                                ObjectCreate(m_id, szName, OBJ_BITMAP_LABEL, 0, 0, 0);                                ObjectSetInteger(m_id, szName, OBJPROP_XDISTANCE, x);                                ObjectSetInteger(m_id, szName, OBJPROP_YDISTANCE, y);                                ObjectSetString(m_id, szName, OBJPROP_BMPFILE, 0, "::" + Resource1);                                ObjectSetString(m_id, szName, OBJPROP_BMPFILE, 1, "::" + (Resource2 == NULL ? Resource1 : Resource2));                        }

好吧,现在每个按钮都将调用此函数来创建。 事情因此变得容易很多,并增加了代码重用度,从而令事情更加稳定和快速。 接下来要创建的是一个函数,该函数将表示要在滑块中使用的通道。 它由以下函数创建:

inline void CreteBarSlider(int x, int size)                        {                                ObjectCreate(m_id, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XDISTANCE, x);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XSIZE, size);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);                                ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);                        }

此处最奇怪的是控制通道边界的表示。 您可以根据需要自定义此设置,以及通道的宽度,该宽度在 OBJPROP_YSIZE 属性中设置。 但在更改此属性的数值时,请不要忘记调整该数值,减去 m_Slider.posY,以便通道位于按钮之间。

创建播放/暂停按钮的函数现在如下所示:

void CreateBtnPlayPause(long id, bool state) {        m_szBtnPlay = def_PrefixObjectName + "Play";        CreateObjectBitMap(5, 25, m_szBtnPlay, def_ButtonPause, def_ButtonPlay);        ObjectSetInteger(id, m_szBtnPlay, OBJPROP_STATE, state); }

容易得多,不是吗? 现在我们来看一下创建滑块的函数。 它如下所示:

void CreteCtrlSlider(void) {        u_Interprocess Info;                                        m_Slider.szBarSlider = def_PrefixObjectName + "Slider Bar";        m_Slider.szBtnLeft   = def_PrefixObjectName + "Slider BtnL";        m_Slider.szBtnRight  = def_PrefixObjectName + "Slider BtnR";        m_Slider.szBtnPin    = def_PrefixObjectName + "Slider BtnP";        m_Slider.posY = 40;        CreteBarSlider(82, 436);        CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft);        CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight);        CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);        ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);        if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = 0;        PositionPinSlider(Info.s_Infos.iPosShift); }

仔细查看控件的名称,这非常重要。 当回放系统处于重播状态时,这些控件将不可用。 每次我们处于暂停状态时,都会调用该函数,从而创建滑块。 请注意因为这一点,赫兹量化将捕获终端全局变量的数值,以便正确识别和定位滑块。

因此,我建议您不要手工对终端的全局变量执行任何操作。 请注意另一个重要细节,即顶针。 与按钮不同,设计它时会在中心有一个锚点,便于查找。 这里我们还有另一个函数调用:

inline void PositionPinSlider(int p) {        m_Slider.posPinSlider = (p < 0 ? 0 : (p > def_MaxPosSlider ? def_MaxPosSlider : p));        ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);        ChartRedraw(); }

它将滑块放置在某个区域中,并确保它驻留在上面的受限设置内。

您可以想象,我们仍然需要对系统进行小的调整。 每次图表时间帧变更时,指标都会重置,导致我们失去在回放系统中存在的实际当前位置点。 避免这种情况的途径之一是在初始化函数里进行一些扩充。 取这些变更的好处,我们还将添加一些其它内容。 我们来看看初始化函数现在是什么样子的:

void Init(const bool state = false) {        if (m_szBtnPlay != NULL) return;        m_id = ChartID();        ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);        CreateBtnPlayPause(m_id, state);        GlobalVariableTemp(def_GlobalVariableReplay);        if (!state) CreteCtrlSlider();        ChartRedraw(); }

现在我们还要添加代码,以便将鼠标移动事件转发到指标。 如果不加这些,鼠标事件就会丢失,并且不会由 MetaTrader 5 传递给指标。 为了在不需要时隐藏滑块,赫兹量化添加了一些检查。 如果检查到确认应显示滑块,那么它就被显示在屏幕上。

到目前为止,我们已经见识到了一切,您可能想知道:事件处理现在如何执行? 我们会有一些超级复杂的额外代码吗? 好吧,处理鼠标事件的方式没有太大变化。 添加拖动事件并不是很复杂。 您真正要做的就是管控一些限制,以免事情失控。 而实现本身非常简单。

赫兹量化来看一下处理所有这些事件的函数的代码:DispatchMessage。 为了便于解释,我们分部分查看代码。 第一部分负责处理对象单击事件。 请看下面的代码:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) {        u_Interprocess Info; //... other local variables ....                                        switch (id)        {                case CHARTEVENT_OBJECT_CLICK:                        if (sparam == m_szBtnPlay)                        {                                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);                                if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else                                {                                        ObjectsDeleteAll(m_id, def_PrefixObjectName + "Slider");                                        m_Slider.szBtnPin = NULL;                                }                                Info.s_Infos.iPosShift = m_Slider.posPinSlider;                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);                                ChartRedraw();                        }else   if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1);                        else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1);                                                                  break; // ... The rest of the code...

当我们按下播放/暂停按钮时,我们需要执行几个操作。 其中之一是如果我们处于暂停状态,则需创建一个滑块。 如果我们退出暂停状态,并进入播放状态,则必须从图表中隐藏控件,从而我们能不再误碰它们。 滑块的当前值应发送到终端的全局变量。 因此,回放服务可以访问我们想要放置回放系统的位置对应的百分比位置。

除了与播放/暂停按钮相关的这些问题外,赫兹量化还需要应对单击滚动条的逐点移动按钮时发生的事件。 如果我们单击滚动条的左按钮,滑块的当前值应该减少 1。 与此类似,如果我们按下滚动条的右按钮,控件当前值加 1,直到最大设置限制。

这很简单。 至少在这一部分中,应对对象点击消息并不难。 然而,现在拖动滑块存在一个稍微复杂的问题。 为了搞清这一点,我们来看一下处理鼠标移动事件的代码。



期货量化交易软件:开发回放系统 — 市场模拟第 04 部分调整设置(II)的评论 (共 条)

分享到微博请遵守国家法律