从头开始开发智能交易系统(第 13 部分):时序与交易(II)
概述
在上一篇文章时序与交易(I)当中,我们讨论了一种替代的图表组织系统,这是创建指标的先决条件,该指标能够最快速地解释市场上所执行的成交。 但我们尚未完成这个系统:它仍然缺乏显示如何访问某些信息的能力,这种访问将有助于更好地了解正在发生的事情。 此类信息不能直接显示在图表上。 实际上,这样的表达是可以实现的,然而解释起来会很混乱。 因此,最好以经典方式表达数据,即,以文本格式的数值。 我们的智能交易系统尚未拥有执行此任务的能力。 因此,我们需要实现它。
为了不让前一篇文章变得复杂,我添加了一些读者可能根本不需要的信息(因为系统可以在不深入了解这些细节的情况下使用),我决定在这里扩展系统,令其更加完整,但我没有将前一篇文章中提出的一些内容包含到新系统之中。 但有时也许需要这些信息来了解市场中实际发生的情况。
计划
此处重要的是要理解一件事。 这只是细节,但俗话说魔鬼生活在细节中。 如此,请看下图:
您是否注意到这张图片中有什么奇怪的地方吗? 也许没有什么意义,但它就在这里,所以请仔细观察。
如果您仍然没有注意到任何奇怪的事情,请参阅下面高亮显示的区域。
现在,您看到发生了什么吗? 在该点处,买卖价格发生了变化,但这里只进行了一笔交易。 即使买卖价格发生变化,也没有理由只进行一笔交易。 但事实上,有些事情比您想象的要普遍。 问题是,当您如下所示采用阅读模式时,通常看不到这种情况:
当采用这种市场分析方法时,我们看不到买卖价格的变化。 市场似乎始终处于运作,每个人都在尝试达成交易,但事实并非如此。 实际上,市场参与者会在某些点位处下单,并等待市场走势。 当触及该位置时,他们试图利获取先手,用价格运动中获利 — 因此,买入或卖出价格会在没有任何交易的情况下移动。 这是一个真实的事实,并可在平台上看到,但大多数人却忽视了这一点,他们认为这些信息并不十分重要。
下图显示了我们的“时序与交易”系统的样子:
如果您仔细观察,您会看到图表上有四种烛条配置。 应该有五种,但直接订单被排除在系统之外,因为它们实际上并未导致市场移动。 故此,我们实际上有四种配置。 这些是以下形式:
阴影有时不触及烛条的主体。 为什么会这样? 阴影是由点差值形成的,而其值又是买入和卖出之间的差值,但如果依据该点差发生操作,那么烛条会是什么样子? 这将是第五种类型,如下所示:
根据其形状类型,它是十字星(DOJI)。 这就是系统中不显示直接订单的原因。 但这并不能解释为什么主体有时不接触阴影。 这种行为与某些情况有关,即发生了导致价格移动过快的情形,主体和阴影之间存在一定间隙。 有人可能会认为这是一个系统故障,因为价格如此波动是没有意义的。 但在此它确实有意义,因为在触发止损单时就会发生这种情形。 为了看清这一点,请参见下图:
有一系列的情况下,有不少订单,但既未触及出价,也没有触及要价。 所有这些点都代表触发的是止损单。 当这种情况发生时,价格通常会跳跃,这在图表上可以很容易看到。 若采用图表模式评估走势时,同样的事实就能在”时序与交易“上看到。 如果没有这一点,您就看不到止损单的触发,也许会认为走势增强了力量,而实际上它可以很快恢复,然后您又会触发止损。
既然您知道了这一点,您就会明白,一个大系列的烛条不触及阴影则代表触发了止损单。 事实上,不可能准确地捕捉到这种走势发生的时间,因为一切都发生得突然。 但是,您可以用出价和要价的解释来找出为何会发生这种情况。 这取决于您和您的市场经验。 我不会深入细节,但如果您真的想用读盘作为指标,这是您应该关注的。
现在,细节来了:如果这些信息只能用烛条看到,且它们本身就足以学到一些信息,那为什么还需要更多的数据呢?
最重要的细节是,有时市场会疲软,在等待某些时刻带来的一些信息,但我们无法通过简单地查看”时序与交易“的烛条来知晓这些。 我们还需要比这更多的东西。 这些信息存在于系统本身,但很难在出现时对其进行解释。 数据理应建模,从而能够更容易地分析。
建模是撰写本文的原因:建模完成后,”时序与交易“将如下所示变化:
换句话说,我们对正在发生的事情将有一个完整的全貌。 甚而,一切都会很快,对于那些想采用读盘作为交易方式的人来说很重要。
实现
为了实现该系统,我们需要向 C_TimesAndTrade 类添加几个新变量。 代码如下所示:
#include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh> #include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh> //+------------------------------------------------------------------+ class C_TimesAndTrade : private C_FnSubWin { //+------------------------------------------------------------------+ #define def_SizeBuff 2048 #define macro_Limits(A) (A & 0xFF) #define def_MaxInfos 257 //+------------------------------------------------------------------+ private : string m_szCustomSymbol, m_szObjName; char m_ConnectionStatus; datetime m_LastTime; ulong m_MemTickTime; int m_CountStrings; struct st0 { string szTime; int flag; }m_InfoTrades[def_MaxInfos]; struct st1 { C_Canvas Canvas; int WidthRegion, PosXRegion, MaxY; string szNameCanvas; }m_InfoCanvas;
加到源代码中的部分以高亮显示。 如您所见,我们需要用到 C_Canvas 类,但它并未拥有我们所需的全部元素。 事实上,我们不得不往这个 C_Canvas 类里添加四个子例程。 这些子例程如以下代码所示:
// ... C_Canvas class code inline void FontSet(const string name, const int size, const uint flags = 0, const uint angle = 0) { if(!TextSetFont(name, size, flags, angle)) return; TextGetSize("M", m_TextInfos.width, m_TextInfos.height); } //+------------------------------------------------------------------+ inline void TextOutFast(int x, int y, string text, const uint clr, uint alignment = 0) { TextOut(text, x, y, alignment, m_Pixel, m_width, m_height, clr, COLOR_FORMAT_ARGB_NORMALIZE); } //+------------------------------------------------------------------+ inline int TextWidth(void) const { return m_TextInfos.width; } //+------------------------------------------------------------------+ inline int TextHeight(void) const { return m_TextInfos.height; } //+------------------------------------------------------------------+ // ... The rest of the code ...
这些行创建文本。 很简单,没什么特别好看的。
该类中值得一提的下一个函数是 C_TimesAndTrade:
void PrintTimeTrade(void) { int ui1; m_InfoCanvas.Canvas.Erase(clrBlack, 220); for (int c0 = 0, c1 = m_CountStrings - 1, y = 2; (c0 <= 255) && (y < m_InfoCanvas.MaxY); c0++, c1--, y += m_InfoCanvas.Canvas.TextHeight()) if (m_InfoTrades[macro_Limits(c1)].szTime == NULL) break; else { ui1 = m_InfoTrades[macro_Limits(c1)].flag; m_InfoCanvas.Canvas.TextOutFast(2, y, m_InfoTrades[macro_Limits(c1)].szTime, macroColorRGBA((ui1 == 0 ? clrLightSkyBlue : (ui1 > 0 ? clrForestGreen : clrFireBrick)), 220)); } m_InfoCanvas.Canvas.Update(); }
该函数将在为其保留的特殊区域中显示数值。 此外,初始化过程也略微发生了变化,可以在下面高亮显示的部分中看到:
void Init(const int iScale = 2) { if (!ExistSubWin()) { m_InfoCanvas.Canvas.FontSet("Lucida Console", 13); m_InfoCanvas.WidthRegion = (18 * m_InfoCanvas.Canvas.TextWidth()) + 4; CreateCustomSymbol(); CreateChart(); m_InfoCanvas.Canvas.Create(m_InfoCanvas.szNameCanvas, m_InfoCanvas.PosXRegion, 0, m_InfoCanvas.WidthRegion, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT), GetIdSubWinEA()); Resize(); m_ConnectionStatus = 0; } ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_CHART_SCALE, (iScale > 5 ? 5 : (iScale < 0 ? 0 : iScale))); }
”时序与交易“的替补程序也需要进行额外的修改。 变化如下:
void Resize(void) { static int MaxX = 0; int x = (int) ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS, GetIdSubWinEA()); m_InfoCanvas.MaxY = (int) ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, GetIdSubWinEA()); ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_YSIZE, m_InfoCanvas.MaxY); if (MaxX != x) { MaxX = x; x -= m_InfoCanvas.WidthRegion; ObjectSetInteger(Terminal.Get_ID(), m_szObjName, OBJPROP_XSIZE, x); ObjectSetInteger(Terminal.Get_ID(), m_InfoCanvas.szNameCanvas, OBJPROP_XDISTANCE, x); } PrintTimeTrade(); }
该系统几乎准备就绪,但我们仍然需要位于系统核心的子例程。 它也经过了修改:
inline void Update(void) { MqlTick Tick[]; MqlRates Rates[def_SizeBuff]; int i0, p1, p2 = 0; int iflag; long lg1; static int nSwap = 0; static long lTime = 0; if (m_ConnectionStatus < 3) return; if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0) { for (p1 = 0, p2 = 0; (p1 < i0) && (Tick[p1].time_msc == m_MemTickTime); p1++); for (int c0 = p1, c1 = 0; c0 < i0; c0++) { lg1 = Tick[c0].time_msc - lTime; nSwap++; if (Tick[c0].volume == 0) continue; iflag = 0; iflag += ((Tick[c0].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY ? 1 : 0); iflag -= ((Tick[c0].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL ? 1 : 0); if (iflag == 0) continue; Rates[c1].high = Tick[c0].ask; Rates[c1].low = Tick[c0].bid; Rates[c1].open = Tick[c0].last; Rates[c1].close = Tick[c0].last + ((Tick[c0].volume > 200 ? 200 : Tick[c0].volume) * (Terminal.GetTypeSymbol() == C_Terminal::WDO ? 0.02 : 1.0) * iflag); Rates[c1].time = m_LastTime; m_InfoTrades[macro_Limits(m_CountStrings)].szTime = StringFormat("%02.d.%03d ~ %02.d <>%04.d", ((lg1 - (lg1 % 1000)) / 1000) % 60 , lg1 % 1000, nSwap, Tick[c0].volume); m_InfoTrades[macro_Limits(m_CountStrings)].flag = iflag; m_CountStrings++; nSwap = 0; lTime = Tick[c0].time_msc; p2++; c1++; m_LastTime += 60; } CustomRatesUpdate(m_szCustomSymbol, Rates, p2); m_MemTickTime = Tick[i0 - 1].time_msc; } PrintTimeTrade(); }
高亮显示的行表示添加到子例程的代码,可令我们针对需要的数据进行建模。 以下代码
lg1 = Tick[c0].time_msc - lTime; nSwap++;
检查交易之间经历了多少以毫秒为单位的时间,以及发生了多少未引起价格变化的交易。 如果这些数字很大,您可以理解换手正在下降。 使用此功能,您会比其他人更早注意到这一点。
以下部分
m_InfoTrades[macro_Limits(m_CountStrings)].szTime = StringFormat("%02.d.%03d ~ %02.d <>%04.d", ((lg1 - (lg1 % 1000)) / 1000) % 60 , lg1 % 1000, nSwap, Tick[c0].volume); m_InfoTrades[macro_Limits(m_CountStrings)].flag = iflag; m_CountStrings++; nSwap = 0; lTime = Tick[c0].time_msc;
将为所要呈现的数值建模。 请注意,由于使用限制,我们不会测试 m_CountStrings 计数器。 随着新信息的出现,我们将简单地增加其数值。 有时这是一个可以使用的技巧。 我自己也在可能的情况下会用到它,因为它在处理方面很高效,这一点很重要,因为交易系统是为实时使用而设计的。 您理应尽可能地优化系统,哪怕只是一点点 — 最终它会产生很大的影响。
在所有事情都实现后,编译智能交易系统,并获得如下内容:
观察“时序与交易”图表上的上述走势,您可看到微观结构开始出现在“时许与交易”本身。 然而,即使在研究了这些微观结构之后,我也无法利用它们存在的事实获取好处。 然而,我不是一个经验丰富的交易者,所以谁知道呢,也许拥有更多经验的人可以做到这一点。
该指标功能强大,信息量大,因此我决定制作一期视频,展示编写时资产显示的真实数据与其数值之间的小规模比较。 我打算演示一下,它过滤了大量信息,令您能够更快地读取数据,更好地了解正在发生的事情。 我希望你们喜欢并能利用这款神奇而强大的指标。
结束语
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10412