English Русский Español Deutsch 日本語 Português
preview
从头开始开发智能交易系统(第 29 部分):谈话平台

从头开始开发智能交易系统(第 29 部分):谈话平台

MetaTrader 5示例 | 10 一月 2023, 09:48
910 0
Daniel Jose
Daniel Jose

概述

我们如何让智能交易系统更有趣些呢? 金融市场交易往往过于无聊和单调,但我们能够令这项工作少些无趣。 请注意,对于那些经历过上瘾等问题的人来说,这个项目可能是危险的。 然而,在一般情况下,它只会让事情聊胜于无。

警告:如果您将市场视为赌博,那么请不要采用本文中给出的修改,因为存在巨亏的真实风险

尽管上面的警告听起来像个玩笑,但事实是,针对 EA 所做的一些修改,对于那些常常沉迷于赌博的人来说是很危险的。

此处即将做出的一些修改旨在提高 EA 的整体稳定性和性能。 如果您想保留我们在本文中删除的一些内容,那么它并不困难。 感谢存在于 EA 中的已创建订单系统,您可以删除一些东西而不会造成任何破坏。 因此,是否接受并使用它,亦或删除它们,均由您定夺。


2.0. 删除 Chart Trade

Chart Trade 在一个简单的订单系统中仍然有其意义,比之我们所用的 EA 系统,它没有那么复杂。 但对于我们处于当前开发阶段的 EA,在图表上加载 Chart Trade 是没有意义的,因此我们可以将其删除。 如果您愿意,只需编辑一个命令即可保留它。 诚然,我喜欢保持简单,以便日后我可以非常快速地修改(或不修改)一些东西,如此这般,修改就不会产生任何压力,例如关键点的问题或灾难性故障。

为了令控制非常快速,且同时保证安全,在 EA 代码中添加了以下定义:

#define def_INTEGRATION_CHART_TRADER            // Chart trader integration with the EA ...

如果此定义不存在或变成了注释,则 CHART TRADE 将不会与 EA 一起编译。 我们来看看受此定义影响的位置。 第一个也是最明显的包括以下:

#ifdef def_INTEGRATION_CHART_TRADER
        #include <NanoEA-SIMD\SubWindow\C_TemplateChart.mqh>
#endif 

虽然上面的代码不在 EA 文件当中,但它在 C_IndicatorTradeView.mqh 文件之中,该定义在代码中随处可见,故我们无需担心调整代码。 在此,我们只需在易于访问的位置创建定义,在本例中即为 EA 代码里,并在必要之处使用它。

但我们继续带着 C_IndicatorTradeView.mqh 文件。 缘于我们可在没有 Chart Trade 的情况下编译 EA,我们需要实现访问 EA 初始化消息框中定义的数据,如下图所示:

请记住,我们需要访问这些数据。 以前我们把它传递至 Chart Trade,且当我们需要知道它们时,我们定位到 Chart Trade。 但现在,没有了 Chart Trade,我们就不得不采取别样的方式来访问相同的数据。

在 C_IndicatorTradeView.mqh 文件中,这些值只在一个地方用到 — 即当我们创建显示挂单位置的指标 0 时。 这个位置在 DispatchMessage 函数内部。 它展示在下面的代码当中:

// ... Previous code ...

                                        case CHARTEVENT_MOUSE_MOVE:
                                                Mouse.GetPositionDP(dt, price);
                                                mKeys   = Mouse.GetButtonStatus();
                                                bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
                                                bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                                                bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                                                if (bKeyBuy != bKeySell)
                                                {
                                                        if (!bMounting)
                                                        {
#ifdef def_INTEGRATION_CHART_TRADER
                                                                m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl);
#else 
                                                                m_Selection.vol = EA_user20 * Terminal.GetVolumeMinimal();
                                                                valueTp = EA_user21;
                                                                valueSl = EA_user22;
                                                                m_Selection.bIsDayTrade = EA_user23;
#endif 
                                                                valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                                                valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_Selection.vol);
                                                                m_Selection.it = IT_PENDING;
                                                                m_Selection.pr = price;
                                                        }

// ... The rest of the code...

注意高亮显示的那些行,无需在文件中搜索这些 EA_userXX 值 — 它们不存在,因为它们来自 EA 代码,如下所示:

#ifdef def_INTEGRATION_CHART_TRADER
        input group "Chart Trader"
#else 
        input group "Base Operacional do EA"
#endif 
input int       EA_user20   = 1;     //Levering factor
input double    EA_user21   = 100;   //Take Profit (financial)
input double    EA_user22   = 81.74; //Stop Loss (financial)
input bool      EA_user23   = true;  //Day Trade ?

仅此一项就已能提供类似于在图表上拥有 Chart Trade 的控制。 请注意,我们实际上不会更改代码中的任何内容。 我们只需将用户定义的所需数据移至正确的位置即可。 有些人也许发现,当加载 EA 时,在交易者部分的这种配置是不必要的,从某种意义上说,这是正确的,因为订单系统能够毫无困难地配置所有变量。 故此,我们能将杠杆因子,和止损、止盈的最小值设置为 0,并将初始操作设置为日内交易 — 这些都在 C_IndicatorTradeView 类的 DispatchMessage 函数中执行。 这根本不会影响系统,因为交易者可以更改图表上的订单,然后将其发送到服务器。 这种类型的修改取决于您,因为它是非常个人化的事情。


2.0.1. 一些调整

在我们回到删除 Chart Trade 部分之前,我们需要再做一件事来提高整个 EA 的稳定性。

我们执行以下操作。 在 C_IndicatorTradeView 类中,我们定义了一个私密数据结构,如下所示:

struct st01
{
        bool    ExistOpenPosition,
                SystemInitilized;
}m_InfoSystem;

必须用以下代码对其进行初始化:

void Initilize(void)
{
        static int ot = 0, pt = 0;
                                
        m_InfoSystem.ExistOpenPosition = false;
        m_InfoSystem.SystemInitilized = false;
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);                             
        if ((ot != OrdersTotal()) || (pt != PositionsTotal()))
        {
                ObjectsDeleteAll(Terminal.Get_ID(), def_NameObjectsTrade);
                ChartRedraw();
                for (int c0 = ot = OrdersTotal(); c0 >= 0; c0--)  IndicatorAdd(OrderGetTicket(c0));
                for (int c0 = pt = PositionsTotal(); c0 >= 0; c0--) IndicatorAdd(PositionGetTicket(c0));
        }
        m_InfoSystem.SystemInitilized = true;
}

为什么我们要在此处创建和初始化这些数据? 请记住,MetaTrader 5 向 EA 发送的事件里,其中一个事件是 OnTick。 若是简单的系统则不会有太多问题。 但是随着系统变得越来越复杂,您需要确保所有一切均能正常工作。 MetaTrader 5 也许会在 EA 准备好处理这些事件之前就向 EA 发送事件。 因此,我们必须确保 EA 准备就绪,我们要创建一些变量来指示 EA 的就绪状态。 如果尚未准备就绪,则应忽略 MetaTrader 5 的事件,直到 EA 能够响应对应的事件。

最关键的一点可在下面的代码中看到:

inline double SecureChannelPosition(void)
                        {
                                static int nPositions = 0;
                                double Res = 0;
                                ulong ticket;
                                int iPos = PositionsTotal();
                                
                                if (!m_InfoSystem.SystemInitilized) return 0;
                                if ((iPos != nPositions) || (m_InfoSystem.ExistOpenPosition))
                                {
                                        m_InfoSystem.ExistOpenPosition = false;
                                        for (int i0 = iPos - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
                                        {
                                                m_InfoSystem.ExistOpenPosition = true;
                                                ticket = PositionGetInteger(POSITION_TICKET);
                                                if (iPos != nPositions) IndicatorAdd(ticket);
                                                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), Res += PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                                        }
                                        nPositions = iPos;
                                }
                                return Res;
                        };

高亮显示的位置以前不存在,故奇怪的事情时有发生。 但现在我们已有必要检查,来确保没有任何异常被忽视。

检查所有一切的问题非常复杂,因为多次检查可令系统非常稳定,但也会让代码维护和修改复杂化。 某些检查必须按逻辑顺序执行才能真正有效,这是一件相当昂贵的事情。

但请注意,当我们检查 EA 监控的交易品种是否存在持仓时,我们可以提供一些敏捷性。 更有甚者,当交易多种资产时,每种资产都有一定数量的持仓将出现在 EA 当中。 经过此处的过滤,我们剔除了循环,如此这般 EA 仅在真正需要时才执行循环中的代码。 否则,我们会略微减少 EA 消耗的处理时间。 它不并长,但在非常极端的情况下,它也会产生很大的差别。


2.0.2. 从 EA 中删除 Chart Trade

现在我们已经对 C_IndicatorTradeView 类进行了修改,我们可以专注于 EA 代码,并从中删除 Chart Trade。 首先要做的是从 OnInit 代码中删除它:

int OnInit()
{       
        Terminal.Init();

#ifdef def_INTEGRATION_WITH_EA
        WallPaper.Init(user10, user12, user11);
        VolumeAtPrice.Init(user32, user33, user30, user31);
        TimesAndTrade.Init(user41);
        EventSetTimer(1);
#endif 

        Mouse.Init(user50, user51, user52);
        
#ifdef def_INTEGRATION_CHART_TRADER
        static string   memSzUser01 = "";
        if (memSzUser01 != user01)
        {
                Chart.ClearTemplateChart();
                Chart.AddThese(memSzUser01 = user01);
        }
        Chart.InitilizeChartTrade(EA_user20 * Terminal.GetVolumeMinimal(), EA_user21, EA_user22, EA_user23);
        TradeView.Initilize();
        OnTrade();
#else 
        TradeView.Initilize();
#endif 
   
        return INIT_SUCCEEDED;
}

绿色代码将被蓝色代码替换。 如果我们不用 Chart Trade 了,则看起来差别很小,仅是可执行文件的大小有变化。 但不仅如此。 请注意,除了 Chart Trade 代码之外,我们还删除了OnTrade 事件。 EA 将不再处理此事件。

您也许会认为我犯了啥错。 何以从 EA 中删除 OnTrade 事件? 我们现在要如何处理交易事件? 这些事件的处理将转至 OnTradeTransaction 事件。 处理方法将比 OnTrade 更有效,这意味着 EA 会更简单、更可靠。

还有一个时刻正在发生变化:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Mouse.DispatchMessage(id, lparam, dparam, sparam);
#ifdef def_INTEGRATION_WITH_EA
        switch (id)
        {
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        WallPaper.Resize();
                        TimesAndTrade.Resize();
        break;
        }
        VolumeAtPrice.DispatchMessage(id, sparam);
#endif 

#ifdef def_INTEGRATION_CHART_TRADER
        Chart.DispatchMessage(id, lparam, dparam, sparam);
#endif 

        TradeView.DispatchMessage(id, lparam, dparam, sparam);  
}

如果 EA 中没有集成,则唯有高亮显示的行会进行编译。 由于这些事件趋于相当恒定的(处理这些事件更有效率、更佳),它们经常与 MetaTrader 5 触发的另一个事件竞争,并交给 EA 处理。 其它事件如下所示:

void OnTick()
{
#ifdef def_INTEGRATION_CHART_TRADER
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]);
#else 
        TradeView.SecureChannelPosition();
#endif 

#ifdef def_INTEGRATION_WITH_EA
        TimesAndTrade.Update();
#endif 
}

这样的事件是一场真正的噩梦,因为它通常被调用多次,在某些情况下,它可以在不到 1 秒的时间内被调用多次。 但是多谢修改了 C_IndicatorTradeView 类的代码,故针对此事件的处理变得更加高效。 稍后我们将进一步提高这种效率,但现在这样就足够了。

好吧,在所有这些更改之后,Chart Trade 就不会集成到 EA 当中了。 我们可以将 Chart Trade 变成一个指标,这会有一些益处,同时令 EA 操作专注于主要活动:处理、定位、和支持订单系统。 但将 Chart Trade 移到指标中涉及一些额外的修改,所以我现在不会展示如何做到这一点,我们将在其它时间在再来看它。 但一般来说,我们可以将 Chart Trade 移到指标之中,且仍然能够通过它发送订单。


3.0. 添加声音

通常我们不会盯盘,但仍然想知道此刻发生了什么。 获得有关某事件通知的方法之一是接收声音警报。 这是最好的警报类型之一,因为它确实能立即吸引我们的注意力。 有时我们已经知道如何通过收听警报来采取行动,而无需检查任何其它消息。

由此,我们来学习一些基本的声音警报设置。 在某些情况下,一句话就能告知特定的内容。 尽管我现在显示的内容和附件中可用的内容仅提供了基本功能,但也许它能激励您增加警报和警告的数量,如此您就不必浪费时间阅读消息。 声音可以指示特定事件,令您在特定交易时刻更敏锐。 相信我,这里的区别很大。

首先要做的是创建一个新文件,该文件将包含新类,它支持和隔离我们的声音系统。 一旦此操作完成后,我们就可开始以非常稳定的方式生产东西。 类的整体可在下面的代码中看到:

class C_Sounds
{
        protected:
                enum eTypeSound {TRADE_ALLOWED, OPERATION_BEGIN, OPERATION_END};
        public  :
//+------------------------------------------------------------------+
inline bool PlayAlert(const int arg)
                {
                        return PlaySound(StringFormat("NanoEA-SIMD\\RET_CODE\\%d.wav", arg));
                }
//+------------------------------------------------------------------+
inline bool PlayAlert(const eTypeSound arg)
                {
                        string str1;
        
                        switch (arg)
                        {
                                case TRADE_ALLOWED   : str1 = def_Sound00; break;
                                case OPERATION_BEGIN : str1 = def_Sound01; break;
                                case OPERATION_END   : str1 = def_Sound02; break;
                                defaultreturn false;
                        }
                        PlaySound("::" + str1);
                        
                        return true;
                }
//+------------------------------------------------------------------+
};

尽管这段代码非常简单,但它里面有些东西还挺有趣。 请注意,我们正在重写 PlayAlert 函数,因此我们有两个版本的相同函数。 这是为什么? 音频系统的工作方式要求我们有两种变化。 对于函数的第一个版本,我们将播放文件中的声音;而在第二个版本中,我们将在函数里播放 EA 内部的声音。 现在,许多人可能不知道该怎么做:直接播放来自音频文件中的声音。 但不用担心,我会告诉您如何做。 推出第一个版本的原因是有些人可能希望将自己的声音或其它声音作为警报,并可随时更改它,而无需重新编译 EA。 事实上,即使 EA 在 MetaTrader 5 中运行时,您也可以更改这些声音。 从播放声音的那一刻起,EA 将调用最后版本,因此您只需将一个音频文件替换为另一个 — EA 甚至不会注意到差异,这会很有帮助。 但是从 EA 代码中可以注意到另一个原因。

实际上,第一个选项用在相当特别的地方,如下面高亮显示的类代码所示:

class C_Router
{
        protected:
        private  :
                MqlTradeRequest TradeRequest;
                MqlTradeResult  TradeResult;
//+------------------------------------------------------------------+
inline bool Send(void)
        {
                if (!OrderSend(TradeRequest, TradeResult))
                {
                        if (!Sound.PlayAlert(TradeResult.retcode))Terminal.MsgError(C_Terminal::FAILED_ORDER, StringFormat("Error Number: %d", TradeResult.retcode));
                        
                        return false;
                }
                return true;
        }

// ... The rest of the class code....

想象一下,处理交易服务器返回的所有可能错误所需的工作量。 但如果我们录制一个音频文件,并根据交易服务器返回的值命名它,EA 就知道要播放哪个文件,这大大简化了我们的生活。 因为在这里我们只需要根据服务器返回的值指定所需文件 — EA 会找到并播放该文件,给我们发送声音警告或语音消息,以便我们确切地知道发生了什么。 太棒了,不是吗? 现在,例如,如果订单被拒绝,平台将以非常清晰的方式通知我们发生了什么,或出了什么问题。 这将以一种非常充分的方式完成,为您呈现特别的东西,这可能是您在市场上交易和行为方式的独家和独特之处。 看看您将获得多少敏锐性,因为在同一音频文件中,您可以明示如何解决问题。

但我们还有第二种操作模式,其中声音存储在可执行的 EA 文件当中。 这是同一函数的第二个版本,现阶段将在 3 个不同的地方调用它来指示 3 种不同类型的事件。 第一个位置可在下面的代码中看到:

int OnInit()
{
        if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        {
                Sound.PlayAlert(C_Sounds::TRADE_ALLOWED);
                return INIT_FAILED;
        }
        
        Terminal.Init();

// ... The rest of the code...

此代码检查平台中是否启用了算法交易。 如果我们忘记启用它,EA 会通知我们它将无法进行交易。 为了检查该选项是否已启用,只需查看平台中的标记,如下图所示:


在第二个地方我们将用到辅助声音,如下所示:

void CreateIndicator(ulong ticket, eIndicatorTrade it)
{
        string sz0;
                                
        switch (it)
        {
                case IT_TAKE    : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break;
                case IT_STOP    : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break;
                case IT_PENDING:
                        macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit);
                        m_BtnCheck.Create(ticket, sz0 = macroMountName(ticket, it, EV_CHECK), def_BtnCheckEnabled, def_BtnCheckDisabled);
                        m_BtnCheck.SetStateButton(sz0, true);
                        macroInfoBase(IT_PENDING);
                        break;
                case IT_RESULT  :
                        macroCreateIndicator(it, clrSlateBlue, clrSlateBlue, def_ColorVolumeResult);
                        macroInfoBase(IT_RESULT);
                        Sound.PlayAlert(C_Sounds::OPERATION_BEGIN);
                        m_InfoSystem.ExistOpenPosition = true;
                        break;
        }
        m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose);
}

每次创建持仓指标时,都会播放声音。 这将令我们的生活更加轻松,因为我们知道挂单已变为持仓,我们需要开始关注它。

第三处也是最后一点,当持仓因任何原因平仓时,我们也有辅助声音。 这是在一个非常特别的地方完成的:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        {
                if (macroGetPrice(ticket, IT_RESULT, EV_LINE) > 0) Sound.PlayAlert(C_Sounds::OPERATION_END);
                ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));
        } else ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)it));
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        m_Selection.ticket = 0;
        Mouse.Show();
        ChartRedraw();
}

您可能会认为当您移除 EA 时,它会播放平仓声音,但事实并非如此。 然而这是不会发生的,因为价格线仍存在于系统之中。 但当平仓时则会发生一些不同的事情,仓位价格线将处于价格水平 0 — 此时会播放声音,表明该笔持仓已平仓。

由于这些声音作为 EA 资源能跟随可执行文件,无论它走到哪里;且如果不重新编译代码就无法修改它们,故此它们也会受到更多限制,但同时它们有助于将 EA 移植到其它地方,而无需我们携带音频文件。

但对于用于提醒故障或错误的声音,逻辑是不同的:它们必须单独移动,并放置在预定位置,以便在需要时可以工作。

附件包含一个名为 SOUNDS 的文件夹。 不要将此文件夹保留在代码所在的同一文件夹中,因为不会播放此文件夹中包含的声音。 将其移动到可以轻松找到的其它位置。 如果您不知道它在哪里,请不要担心 — 我们稍后会看到它:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
void OnStart()
{
        Print(TerminalInfoString(TERMINAL_PATH));
}
//+------------------------------------------------------------------+

运行此脚本时,您将在工具栏中获得指示我们将使用的位置的信息。 下图显示了执行结果的示例:

您应该执行以下操作:

  1. 打开附件
  2. 打开文件资源管理器
  3. 进入上图所示的文件夹
  4. 将 SOUND 文件夹的内容从附件复制到上面指示的文件夹
  5. 如果需要,您可以删除这三个文件(WARRING,BEGIN,END),因为它们将与 EA 一起编译
  6. 如果需要,您可将 .WAV 的内容更改为您喜欢的东西;只需确保不更改名称
  7. 在 MetaTrader 5 平台中使用 EA,并感到欣慰!
但请记住,为了在 EA 中编译声音(WARRING、BEGIN、END),您应该在 MQL5 代码目录的 SOUND 文件夹中含有相同的声音文件,否则它们将不会集成到 EA 代码当中。


结束语

在本文中,您学习了如何将自定义声音添加到 EA 系统。 在此,我们用到了一个非常简单的系统来演示如何做到这一点,但您不仅限于 EA,您还可以在指标、甚至脚本中使用它。
最棒的是,如果您运用此处提出的相同概念和思路,就可以录制语音消息,述说或警告某事。 当 EA,或任何其它进程正在 MetaTrader 5 中执行,并用到声音系统,其由本文中所示的触发器激活时,您将收到这样一条语音消息,通知您或警告您,您已经碰到了预见的情况,并应该做出某种行动。
这不是一条文本, 而是语音消息,这令它更有效,因为您可以快速通知使用该系统的任何人,解释应该如何应对,或者造成此类消息的原因是什么。

这个系统不局限于这个方案,您可以超越这里演示的内容。 这个思路准确来讲:允许用户在平台中拥有一个盟友。 通过与交易者进行语音交互,我们能够传达比纯文本更易于理解的信息。 此处唯一限制您的是您的创造力。
   


本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10664

附加的文件 |
EA_-_h_Parte_29_6.zip (14465.62 KB)
从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标? 从头开始开发智能交易系统(第 30 部分):CHART TRADE 当作指标?
今天我们将再次用到 Chart Trade,但这回它作为一个图表上的指标,或许也可能不在图表上出现。
DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象 DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象
在本文中,我将创建一种功能,可隐藏超出其容器之外的对象部分。 此外,我亦将创建辅助箭头按钮对象,作为其它 WinForms 对象的一部分。
从头开始开发智能交易系统(第 31 部分):面向未来((IV) 从头开始开发智能交易系统(第 31 部分):面向未来((IV)
我们继续从 EA 中删除单独的部件。 这是本系列中的最后一篇文章。 并且最后要移除的是声音系统。 如果您之前没有关注过这些文章系列,可能会有点困惑。
学习如何基于 DeMarker 设计交易系统 学习如何基于 DeMarker 设计交易系统
此为我们系列中的一篇新文章,介绍如何基于最流行的技术指标设计交易系统。 在本文中,我们将介绍如何基于 DeMarker 指标创建交易系统。