概论
有人认为, 技术分析是科学和艺术的结合。这种双重性背后的原因就在于每位交易员和分析师的均有各自不同的观点。例如, 完全一样的趋势线可以有完全不同的绘制方法。在依赖精确为关键点的商品交易中, 这种不确定性是令人难以接受的。所有尝试生成趋势线的交易员也遇到了这个问题, 他们都曾发现有若干种方式来实现它。这种障碍不利于创建基于分析行情趋势线的精准交易系统。还有多重原因导致的其它问题: 搜索局部极值时的差异, 基于不正确构造的趋势线的离散和聚合。
但并并非所有的都接受这种过于灵活的技术分析方法。例如, Thomas DeMark 设法找到针对这一问题的分析方法, 并提出解决它的方式。在他名为 "技术分析的新科学" 的著作中, 他描述了当前价格形势更准确的分析方法。在本文中, 我将告诉您他的两个有关发现 — TD 点和 TD 线。所有一切表明, 这不仅是 Thomas DeMark 一书的主题: 他还涵盖了行情周期, 埃洛特波浪, 以及更多。
本文还介绍并解释了编写三款指标, 以及两款以 Thomas DeMark 的思路为基准的智能交易程序的过程。我相信, 这篇文章将会吸引许多交易员, 特别是外汇新手。
1. TD 点
Thomas DeMark 的第一项发明简化了寻找构建趋势线所需价格极值的过程。他决定使用日线图表来寻找最大价格的蜡烛条, 即高于前一天, 且高于随后 定义天数 (我将使用这个词来指代蜡烛条用于确定 TD 点存在)。如果满足该条件, 则可在图表上构建基于定义蜡烛条最大价格上的 TD 点。因此, 如果定义天数的最小值低于之前一天和随后几天的最小值, 则可在图表上构建基于定义蜡烛条最小价格上的 TD 点。
图例. 1. 牛市和熊市 TD 点
以上第一副插图显示了一个级别 1 的 TD 点 (它以红色标记)。如图所示, 定义蜡烛的最大值比之前和随后的蜡烛条更大。最大值出现在图中灰色水平线的位置。第二幅插图显示类似情况, 但它是熊市 TD 点。规则满足相同的方式: 定义蜡烛的最小值低于之前和随后蜡烛的最小低。
仅考虑以上级别 1 的 TD 点。这意味着, 定义蜡烛的价格仅与前一根和后一根进行比较。如果需要构建一个级别2 的 TD 点, 则定义蜡烛的最高价格必须与前两根和随后两根蜡烛相比较。以此类推, 同样适用于最低价格。
图例. 2. 级别 2 的 TD 点举例
以上插图显示级别 2 的 TD 点定义蜡烛的最高价格明显高于前两根和随后两根蜡烛的最大价格。
图例. 3. 级别 40 的 TD 点
也许有许多超过两级的 TD 点, 取决于针对定义蜡烛条进行比较的最大和最小值的量。一个级别 3 的 TD 点只是逻辑上的, 例如, 同时是两个低位的点 — 第二个和第一个。在本书中, Thomas DeMark 涵盖的点达到级别 3。
值得注意的是, 依据此原理操作的指标已经存在了很长时间。事实上, 比尔·威廉姆斯的分形即是低于级别 2 的 TD 点。我印象中它们的建立规则是: 定义蜡烛条是前后两根蜡烛条的最小或最大值, 这完全符合级别 2 的 TD 点定义。
2. TD 线
TD 点只是简单的单独极值。我们将需要两个点 (2 个最大值或 2 个最小值) 来建立一条趋势线。同时, Thomas DeMark 只使用 最后 两个明显的 TD 点。
图例. 4. 级别 1 TD 线/级别 2 TD 线。
左边的插图显示两个级别 2 的 TD 线 (一条蓝色 — 最小, 一条绿色 — 最大)。线的级别取决于建立此线的 TD 点级别。右边的插图显示级别 3 的 TD 线。
Thomas DeMark 已经开发了 3 中价格项目可以直接链接到 TD 线, 我将在 "附加信息" 一节中简要触及该主题。
3. 创建指标
3.1. iTDDots
手工为每根蜡烛条建立新的点和线是相当累人的。我相信, 如果有些事可以在不影响质量的情况下能够自动化, 那么就应该这样做。创建构建在 TD 点上的指标过程将在下面描述。在这个例子中, 我们使用 MQL4 语言操作。
首先, 指标方案已定案。当前蜡烛条在指标操作时不予考虑, 因为其最大/最小值尚未确定, 会导致构筑点错误。因此, 只有以前的蜡烛条才会考虑。
指示操作计划:
- 当挂载到图表上时, 基于存在的历史数据建立所有 TD 点。它们的级别由用户设置。
- 检查每根新蜡烛条建立新 TD 点的可能性, 如果出现则进一步处理。
指标的主要任务是确定一根蜡烛的极值高于相邻 n 根蜡烛条的极值。这就是为什么我建议编写一个函数来确定我所需要的一系列蜡烛条的最高和最低价格。为了确定每次应检查多少根蜡烛条, 我将点数的级别乘以 2, 再加 1 作为结果。
图例. 5. 指标所使用的蜡烛条
以上插图显示如何通过指标按编号计算蜡烛条。它清晰地展示了其数额计算方法。
现在, 我会为您呈现代码是如何开发的。
指标必须有两个缓存区来显示点, 因为一根蜡烛可以在同时即是 TD 点的最大值也可以是最小值。所以, 这就是程序如何开始的:
#property indicator_chart_window //在图表窗口里显示指标 #property indicator_buffers 2 //使用 2 个缓存区 #property indicator_plots 2 //2 个缓存区将用作显示 #property indicator_color1 clrGreen //第一个缓存区的标准颜色 #property indicator_type1 DRAW_ARROW //第一个缓存区的绘图类型 #property indicator_width1 2 //第一个缓存区显示的标准线宽 #property indicator_color2 clrBlue //第二个缓存区的标准颜色 #property indicator_type2 DRAW_ARROW //第二个缓存区的绘图类型 #property indicator_width2 2 //第二个缓存区显示的标准线宽
用户必须确定挂载到图表时指标创建 TD 点的级别:
input int Level = 1;
用于指示操作的以下变量声明为全局:
bool new_candle = true; double pdH, //定义蜡烛条的最大价格 pdL, //定义蜡烛条的最小价格 pricesH[], //存储最大价格的数组 pricesL[]; //存储最小价格的数组 bool DOTH, //显示一个点 (基于最大值) DOTL; //显示一个点 (基于最小值) double UpDot[], //基于最大值的绘图点缓存区数组 DownDot[]; //基于最小值的绘图点缓存区数组
函数 Init() 如下:
int OnInit() { ChartRedraw(0); //当切换时间帧时刷新图表 SetIndexBuffer(0, UpDot); SetIndexBuffer(1, DownDot); SetIndexEmptyValue(0,0.0); SetIndexEmptyValue(1,0.0); SetIndexArrow(0,159); //设置 Wingdings 字体的符号代码 SetIndexArrow(1,159); SetIndexLabel(0, "TD " + Level + " High"); //这些名字将被显示在数据窗口 SetIndexLabel(1, "TD " + Level + " Low"); return(INIT_SUCCEEDED); }
为了获得所需的蜡烛条价格, 我已创建了一个函数, 您可以在下面看到:
void GetVariables(int start_candle, int level) { /*在此函数里, 指标从蜡烛条中收集建立 TD 点的信息。此处使用的所有变量已经全部声明为全局*/ pdH = iHigh(NULL, 0, start_candle + 1 + level); //定义蜡烛的最高价 pdL = iLow(NULL, 0, start_candle + 1 + level); //定义蜡烛的最低价 ArrayResize(pricesH, level * 2 + 1); //设置数组大小 ArrayResize(pricesL, level * 2 + 1); // for (int i = level * 2; i >= 0; i--){ //收集数组中所需蜡烛的所有价格 (最大值和最小值) pricesH[i] = iHigh(NULL, 0, start_candle + i + 1); pricesL[i] = iLow(NULL, 0, start_candle + i + 1); }
最后, 最有趣的 — start() 函数的代码:
int start() { int i = Bars - IndicatorCounted(); //避免出现新蜡烛条时的重复计数 for (; i >= 0; i--) { / 所/有 TD 点相关的活动在这里进行 DOTH = true; DOTL = true; GetVariables(i, Level); //获取当前价格值 for(int ii = 0; ii < ArraySize(pricesH); ii++) { //判断在此间隔是否为 TD 点 if (pdH < pricesH[ii]) DOTH = false; if (pdL > pricesL[ii]) DOTL = false; } if(DOTH) UpDot[i + Level + 1] = pdH; //如果是, 则其构造如下 if(DOTL) DownDot[i + Level + 1] = pdL; if(UpDot[i + Level + 1] == UpDot[i + Level + 2]) UpDot[i + Level + 2] = 0; //在此, 我涵盖了两根蜡烛条的情况 if(DownDot[i + Level + 1] == DownDot[i + Level + 2]) DownDot[i + Level + 2] = 0; //同时有最大值和最小值, 建立一个 TD 点 } //在最后一对蜡烛条 return(0); }
所以, 我们已经掌握了编写指标创建 TD 点的过程。两个加载 TDDots 指标的图表显示如下。第一个图表 Level = 1, 第二个图表 — Level = 10。这意味着, 在第一个图表上所有 TD 点至少围绕着一根低于最大值或高于最小值的蜡烛条, 而第二个图表上则是 10。这些图表只为展示指标是如何操作的。
图例. 6. 指标操作示例: 创建级别 1 的 TD 点和级别 10 的 TD 点。
3.2. iTDLines
正如我先前所说, Thomas DeMarke 只用最后两个点来绘制一条 TD 线。我将会在我的指标里自动完成这种方法。指标的目标是通过设定点建立两条直线。问题是如何建立它们。当然, 您可以使用类型的线性函数: y = kx + b。系数 k 和 b 必须要选择, 因此该线将按设置点严格经过。
两个点的坐标为已知, 以便通过它们建立直线。采用以下公式, 我们会发现线性函数的 k 和 b。其中 x 是我们从图表右侧标记蜡烛条的号码, 而 y — 价格。
b = (x2 * y1 - x1 * y2) / (x2 - x1),
此处 x1 - 第一个点的蜡烛条号码,
x2 - 第二个点的蜡烛条号码,
y1 - 第一个点的价格,
y2 - 第二个点的价格。
知道了 k 和 b, 剩下的就是为每根蜡烛条解一个简单的线性方程来获得 TD 线上每一点的价格。
指标的代码提供如下:
#property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 #property indicator_color1 clrGreen #property indicator_color2 clrBlue input int Level = 1; double LU[], LD[]; //此变量用于最小值的计算。 datetime LastCount;
一个变量, 它是指标用来建立直线的支点级别值。它由用户设置, 与 TDDots 的方式相同。这之后的两个数组包含线上所有点的价格。指标使用变量 LastCount 为每根蜡烛条进行一次计算。
即时查找每条线系数 k 和 b 值的函数将在稍后呈现。它将会分部分描述:
void GetBK(double &Ub, double &Uk, double &Db, double &Dk, int &EndUN, int &EndDN) { double TDU[]; double TDD[]; int TDU_n[]; int TDD_n[]; ArrayResize(TDU, 2, 2); ArrayResize(TDD, 2, 2); ArrayResize(TDU_n, 2, 2); ArrayResize(TDD_n, 2, 2);
函数返回六个数值。前四个分配的变量很清楚, 后两个变量显得有些复杂。我们发现它们对于显示直线很有用。它们中的每一个都表示线段在蜡烛条里的长度, 从当前一根开始。
//接受支点的价格值, 以及自开头的蜡烛条号码 int Ui = 0; int Di = 0; for(int i = 0;; i++) { double current_bar_U = iCustom(NULL, 0, "TDDots", Level, 0, i); double current_bar_D = iCustom(NULL, 0, "TDDots", Level, 1, i); if(current_bar_U > 0 && Ui < 2) { TDU[Ui] = current_bar_U; //价格 TDU_n[Ui] = i; //号码 Ui++; } if(current_bar_D > 0 && Di < 2) { TDD[Di] = current_bar_D; TDD_n[Di] = i; Di++; } if(Ui == 2 && Di == 2) break; }
这部分代码接收最后两个 TD 点的数值。价格保存在数组, 今后会利用它们进行操作。
Ub = ( (TDU_n[0] * TDU[1]) - (TDU[0] * TDU_n[1]) ) / ( TDU_n[0] - TDU_n[1] ); Uk = (TDU[0] - TDU[1]) / (TDU_n[0] - TDU_n[1]); Db = ( (TDD_n[0] * TDD[1]) - (TDD_n[1] * TDD[0]) ) / ( TDD_n[0] - TDD_n[1] ); Dk = (TDD[0] - TDD[1]) / (TDD_n[0] - TDD_n[1]); EndUN = TDU_n[1]; EndDN = TDD_n[1]; }
因为蜡烛条在时间序列上编号 (从右至左), 将使用点的相反值。换言之, 上述公式内的 x2 将被 x1 替换, 而 x1 则由 x2 替代等等。这是将会看到的::
b = (x1 * y2 - x2 * y1) / (x1 - x2),
k = (y1 - y2) / (x1 - x2),
此处 x1 - 第一个支点的蜡烛条号码,
x2 - 第二个支点的蜡烛条号码,
y1 - 第一个点的价格,
y2 - 第二个点的价格。
它随后是 OnInit() 函数:
int OnInit() { SetIndexBuffer(0, LU); SetIndexLabel(0, "TDLU"); SetIndexBuffer(1, LD); SetIndexLabel(1, "TDLD"); SetIndexEmptyValue(0, 0); SetIndexEmptyValue(1, 0); LastCount = iTime(NULL, 0, 1); return(INIT_SUCCEEDED); }
在此函数里, 缓存区被初始化并被命名。此外, 为了能让指标在新数据的第一笔分时来到是计算线的位置, 前一根蜡烛条的数据已被记录在 LastCount 变量里。事实上, 来自当前蜡烛条任何部分的数据, 均可在这里写入。
然后, 我们将编写 start() 函数:
int start() { //新蜡烛或首次启动 if(iTime(NULL, 0, 0) != LastCount) { double Ub, Uk, Db, Dk; int eUp, eDp; GetBK(Ub, Uk, Db, Dk, eUp, eDp); //删除旧数值 for(int i = 0; i < IndicatorCounted(); i++) { LU[i] = 0; LD[i] = 0; } //建立新数值 for(i = 0; i <= eUp; i++) { LU[i] = Uk * i + Ub; } for(i = 0; i <= eDp; i++) { LD[i] = Dk * i + Db; } LastCount = iTime(NULL, 0, 0); } return 0; }
在绘制新数值之前, 需要清除图表上的旧数值。所以, 在函数的开始实现了一段单独循环。现在, 根据上述方程, 两条线被建立, 且 LastCount 变量赋值为当前日期, 以避免重复在当前蜡烛上执行这些操作。
其结果是, 指标据此操作:
图例. 7. 指标操作示例: 基于级别 5 的 TD 点建立 TD 线。
这不难理解, 图例. 7 显示了变量值 Level = 5 时指标的操作。
3.3水平线指标
当然, 有许多方法来确定图表上水平级别的价格。我推荐一个依据当前价格建立两个级别的简单方法。iTDDots 是我已经编写好的, 并将在指标里用来操作 (参看p. 3.1)。
关键很简单:
- 指标获取由用户设定的确定级别 TD 点的 n 个数值
- 计算这些点的平均价格
- 基于它的水平线显示在图表上
然而, 有种情况是 TD 点距最后一个点很远, 水平级别严重偏离当前价格。为了解决这个问题, 实现方法就是由用户引入一个降低点之间距离的变量。这意味着, 指标查找最后 n 个极值之间的平均值, 这个点间距离不会超过一定的极点数量。
让我们来看看指标代码。
2 个缓存区的数值和 3 个变量, 用户可输入:
#property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 #property indicator_color1 clrGreen #property indicator_type1 DRAW_LINE #property indicator_width1 2 #property indicator_color2 clrBlue #property indicator_type2 DRAW_LINE #property indicator_width2 2 input int TDLevel = 1; //点的级别 input int NumberOfDots = 3; //点的数量 input double Delta = 0.001; //两点之间最大距离 double TDLU[], TDLD[];
在此指标里创建了两条水平线。因为它们也许会在相当长的分析时间里保持数值 (超过在 TDLines 指标里的趋势线), 我们决定使用图形对象来创建这些水平线。然而, 如果将水平级别值保存在指标缓存区里, 在未来可以很容易使用这个指标。我已决定把这些值存储在索引为 0 的目前蜡烛条上, 所以我总有简单方法可以通过从其它程序里调用 iCustom 来获取价位值。
int OnInit() { SetIndexBuffer(0,TDLU); SetIndexBuffer(1,TDLD); SetIndexEmptyValue(0,0.0); SetIndexEmptyValue(1,0.0); SetIndexLabel(0, "U HL"); SetIndexLabel(1, "D HL"); ObjectCreate(0, "U 水平级别", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0); ObjectCreate(0, "D 水平级别", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0); return(INIT_SUCCEEDED); }
计算指定水平级别价格的函数如下。
double GetLevelPrice(int ud,int n,double delta,int level) { /* ud - 指标线型。0 - U, 其它值 - D。 n - 极点数量。 delta - 它们之间的最大距离 level - 点级别。*/ //准备保存极点价格的数组 double TDU[]; double TDD[]; ArrayResize(TDU,n,n); ArrayResize(TDD,n,n); ArrayInitialize(TDU,0); ArrayInitialize(TDD,0); //循环操作两次, 因为只存在两个数据缓存区 for(int Buffer=0; Buffer<2; Buffer++) { int N=0; int Fails=0; bool r=false; for(int i=0; r==false; i++) { double d=iCustom(NULL,0,"TDDots",level,Buffer,i); if(d>0) { if(N>0) { if(Buffer==0) double cp=TDU[N-1]; else cp=TDD[N-1]; if(MathAbs(d-cp)<=delta) { if(Buffer == 0) TDU[N] = d; else TDD[N]=d; N++; } //如果距离太远, 则在错误中加 1 else { Fails++; } } else { if(Buffer == 0) TDU[N] = d; else TDD[N]=d; N++; } } //如果错误太多, 循环终止 if(Fails>2 || N>n) r=true; } } //获得平均值 double ATDU = 0; double ATDD = 0; N=0; for(i=0; i<ArraySize(TDU); i++) { ATDU=ATDU+TDU[i]; if(TDU[i]==0) { i=ArraySize(TDU); } else { N++; } } ATDU=ATDU/N; N=0; for(i=0; i<ArraySize(TDD); i++) { ATDD=ATDD+TDD[i]; if(TDD[i]==0) { i=ArraySize(TDD); } else { N++; } } ATDD=ATDD/N; //函数返回值 if(ud == 0) return ATDU; else return ATDD; }
每次新分时调用的函数里, 只简单地保留获取价格值并将它们分配给已创建对象:
void start() { //从指标缓存区里删除前一根蜡烛条数值 TDLD[1] = 0; TDLU[1] = 0; TDLD[0] = GetLevelPrice(1, TDLevel, Delta, NumberOfDots); TDLU[0] = GetLevelPrice(0, TDLevel, Delta, NumberOfDots); //如果对象不知何故消失了 if(ObjectFind("U 水平级别") < 0) { ObjectCreate(0, "U 水平级别", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0); } if(ObjectFind("D 水平级别") < 0) { ObjectCreate(0, "D 水平级别", OBJ_HLINE, 0, iTime(NULL, 0, 0), 0); } ObjectSetDouble(0, "U 水平级别", OBJPROP_PRICE, TDLU[0]); ObjectSetDouble(0, "D 水平级别", OBJPROP_PRICE, TDLD[0]); }
为了使用的便利性, 删除指标对象后还应删除:
void OnDeinit(const int reason) { if(!ObjectDelete("U 水平级别")) Print(GetLastError()); if(!ObjectDelete("D 水平级别")) Print(GetLastError()); }
最终, 所创建指标提供了一种简单方法, 可基于用户设定的参数自动构建水平线。
图例. 8. 指标操作示例。
4. 遵照水平线指标进行交易的智能程序
任何指标应当可以用于赚取利润, 如果这样的机会是存在的, 则应优选自动化。显然, 在本文中描述的智能交易程序无法在任何时间和任何行情下带来盈利, 但其创造背后的目标是不同的。它只是为展示指标在行动中的机会而被编入, 以及示意智能交易程序的一般结构。
现在, 它将会更具体。使用水平线指标, 我们可以编写一款基于下述信号进行交易的智能程序。
买入条件:
- 价格突破水平级别上边界
- 自当前水平级别上边界的价格增长 n 个点
卖出条件则为镜像:
- 价格突破水平级别下边界
- 自当前水平级别下边界的价格降低 n 个点
否则, 可能如下图所示:
图例. 9. 买入和卖出条件
智能交易程序只会开一单并伴随着尾随停止, 即, 它将根据用户在启动时设置的点数移动止损位。
离场将完全依靠止损来执行。
让我们来看看用 MQL4 语言开发的智能交易程序代码。
首先, 我们将描述变量, 它们的数值在程序运行时由用户设置。
input int MagicNumber = 88341; //智能交易程序开单时所用的魔幻数字 input int GL_TDLevel = 1; //水平级别指标所用的 TD 点级别 input int GL_NumberOfDots = 3; //水平级别指标所用的点数 input double S_ExtraPoints = 0.0001;//附加点数 input double GL_Delta = 0.001; //水平级别指标认可的 TD 点距离 input int StopLoss = 50; //止损位 input double Lot = 0.01; //手数大小
接下来的函数检查 TD 线是否交叉。为了避免每次现价与 iglevels 指标所建立的线交叉时重复开仓, 我们检查前一根蜡烛的 最高价 上穿上边界, 或是前一根蜡烛的 最低价 下穿下边界。这样一来, 在一根蜡烛条上获取单次信号的机会得以实现。函数代码的更深呈现:
int GetSignal(string symbol,int TF,int TDLevel,int NumberOfDots,int Delta,double ExtraPoints) { //建立 TD 点上边界的价格值 double UL=iCustom(symbol,TF,"iglevels",GL_TDLevel,GL_NumberOfDots,GL_Delta,0,0)+ExtraPoints; //...TD 点的下边界 double DL=iCustom(symbol,TF,"iglevels",GL_TDLevel,GL_NumberOfDots,GL_Delta,1,0)-ExtraPoints; if(Bid<DL && iLow(symbol,TF,1)>DL) { return 1; } else { if(Ask>UL && iHigh(symbol,TF,1)<UL) { return 0; } else { return -1; } } }
为了智能交易程序的操作, 下列变量必须声明为全局范围:
int Signal = -1; //当前信号 datetime LastOrder; //最后执行交易的日期。为避免在一根蜡烛条上重复开单的情况, 这是必需的。
函数 Init() 应将最近日期赋值给 LastOrder 变量以便开新单。
int OnInit() { LastOrder = iTime(NULL, 0, 1); return(INIT_SUCCEEDED); }
包括最重要行动的 OnTick 函数示意如下:
void OnTick() { bool order_is_open=false; //搜索已开订单 for(int i=0; i<OrdersTotal(); i++) { if(!OrderSelect(i,SELECT_BY_POS)) Print(GetLastError()); if(OrderMagicNumber()==MagicNumber) { order_is_open=true; break; } } //获取当前信号 Signal=GetSignal(Symbol(),0,GL_TDLevel,GL_NumberOfDots,GL_Delta,S_ExtraPoints); //计算止损大小 double tsl=NormalizeDouble(StopLoss*MathPow(10,-Digits),Digits); if(order_is_open==true) { //计算止损价格 double p=NormalizeDouble(Ask-tsl,Digits); if(OrderType()==1) p=NormalizeDouble(Ask+tsl,Digits); if(OrderType()==0 && OrderStopLoss()<p) { if(!OrderModify(OrderTicket(),OrderOpenPrice(),p,0,0)) Print(GetLastError()); } if(OrderType()==1 && OrderStopLoss()>p) { if(!OrderModify(OrderTicket(),OrderOpenPrice(),p,0,0)) Print(GetLastError()); } } //如果没有订单 if(order_is_open==false) { //如果在当前蜡烛条尚未开单 if(iTime(NULL,0,0)!=LastOrder) { //买入 if(Signal==0) { if(!OrderSend(NULL,0,Lot,Ask,5,Ask-tsl,0,NULL,MagicNumber)) Print(GetLastError()); LastOrder=iTime(NULL,0,0); } //卖出 if(Signal==1) { if(!OrderSend(NULL,1,Lot,Bid,5,Ask+tsl,0,NULL,MagicNumber)) Print(GetLastError()); LastOrder=iTime(NULL,0,0); } } } }
它用于检查存在的已开订单, 如果有, 则智能交易程序检查移动止损位的可能性。如果没有订单, 则根据 GetSignal 函数得到的信号进行操作。重要提示: 如果您计划在程序里比较两个实数, 事先使用 NormalizeDouble 函数, 否则您可能获得完全不合逻辑的比较结果。
智能交易程序据此操作:
图例.10. 智能交易程序在策略测试器里的操作示例
图例. 10 显示亏损和盈利的仓位。盈利超过亏损, 因为每个设定条件的亏损是有限的, 盈利交易伴随着尾随止损。EA 可以显示良好和失望的结果。不过, 我们需要记住, 它并未创造真实交易, 而只是用来展示应用 TD 点的机会, 以及基于此创建指标。我们的智能交易程序能够处理这个任务。
图例.11. 测试结果
这款 EA 的改进可添加部分平仓达成锁定盈利, 以避免不时发生的趋势调整导致转盈为亏。在这种情况下, 仓位平出, 有利于保本。出于公平提示, 止损距离必须依据价格变化的活性修改。
5. 依据 TD 线进行交易的智能程序
这款智能程序是使用 TD 线创造交易系统的一个示例。通常, TD 线为趋势线。这意味着, 如果价格与 TD 线交叉, 且与前进方向同向移动, 则趋势可能会变化。
智能交易程序只有两个信号:
图例.12. 根据 etdlines 进行交易的信号
如果上穿牛市 TD 线, 且在同一方向上超过若干点 (由用户设置), 则开多头仓位。此外, 一条 TD 线应指向朝下, 即线的最小价格数值对应于日期靠后的蜡烛条。
与此对称, 如果下穿熊市 TD 线, 且在同一方向上超过若干点 (由用户设置), 则开空头仓位。TD 线指向朝上。
The position is accompanied with a fixed size Trailing Stop. 平仓仅由止损执行。
以下您将会发现用户输入变量的代码:
input int Level=1; //TD 线级别 input double Lot=0.01; //手数大小 input int AddPips=3; //附加点数 input int Magic=88342; //订单的魔幻数字 input int Stop=50; //止损的点数 input int Bars_To_Open_New_Order=2;//开单的柱线间隔 datetime LastBar; datetime Trade_is_allowed;
在 EA 中, 平旧仓与开新单之间有一定延迟。这是防止开、平仓过于频繁。
EA 在 OnInit 函数里为 LastBar 和 Trade_is_allowed 变量赋值:
int OnInit() { LastBar=iTime(NULL,0,1); Trade_is_allowed=iTime(NULL,0,0); return(INIT_SUCCEEDED); }
下一个函数 GetSignal 返回一个交易信号:
int GetSignal() { //如果新蜡烛条生成 if(LastBar!=iTime(NULL,0,0)) { double DU = iCustom(NULL, 0, "itdlines", Level, 0, 0); double DD = iCustom(NULL, 0, "itdlines", Level, 1, 0); double DU1 = iCustom(NULL, 0, "itdlines", Level, 0, 1); double DD1 = iCustom(NULL, 0, "itdlines", Level, 1, 1); } double add_pips=NormalizeDouble(AddPips*MathPow(10,-Digits),Digits); //上边线突破 --> 买入 if(Ask>DU+add_pips && iLow(NULL,0,0)<Ask && DU<DU1) { return 0; } else { //下边线突破 --> 卖出 if(Bid<DD-add_pips && iHigh(NULL,0,0)>Bid && DD>DD1) { return 1; } //无突破 --> 忽略信号 else { return -1; } } return -1; }
变量 LastBar 用于判断新的蜡烛条。需要避免每笔新分时都要计算 TD 线的两个支点, 因为当新蜡烛条出现时, 它们只需计算一次。函数返回: 0 — 买入, 1 — 卖出, -1 — 无信号。
最后, 在 OnTick 函数里包括所有放置订单操作:
void OnTick() { int signal=GetSignal(); bool order_is_open=false; //搜索已开单 for(int i=0; i<OrdersTotal(); i++) { if(!OrderSelect(i,SELECT_BY_POS)) Print(GetLastError()); //通过魔幻数字 if(OrderMagicNumber()==Magic) { order_is_open=true; i=OrdersTotal(); } } //止损距离 double stop=Stop*MathPow(10,-Digits); //如果订单以开仓 if(order_is_open==true) { //检查移动止损的可能性 //止损价位 double order_stop=NormalizeDouble(OrderStopLoss(),Digits); //如果买入订单 if(OrderType()==0) { if(order_stop<NormalizeDouble(Ask-stop,Digits)) { if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Ask-stop,Digits),0,0))Print(GetLastError()); } } //如果卖出订单 if(OrderType()==1) { if(order_stop>NormalizeDouble(Bid+stop,Digits)) { if(!OrderModify(OrderTicket(),OrderOpenPrice(),NormalizeDouble(Bid+stop,Digits),0,0))Print(GetLastError()); } } Trade_is_allowed=iTime(NULL,0,0)+ChartPeriod(0)*60*Bars_To_Open_New_Order; } //如果尚未开单 else { if(signal>=0 && iTime(NULL,0,0)>Trade_is_allowed) { if(signal==0) { if(!OrderSend(NULL,signal,Lot,Ask,5,Ask-stop,0,NULL,Magic)) Print(GetLastError()); } if(signal==1) { if(!OrderSend(NULL,signal,Lot,Bid,5,Bid+stop,0,NULL,Magic)) Print(GetLastError()); } } } }
首先, 计算信号, 循环搜索订单, 其魔幻数字与用户设置的相等 (要考虑到无法匹配其它系统)。然后, 如果有已开订单, 检查移动止损的机会, 并按此条件执行。如果没有订单, 则依据信号开新单, 但前提条件是, 当前蜡烛条的数据大于 Trade_is_allowed 变量 (当有已开订单时, 其值在每笔新分时来临时都有变化)。
EA 交易依据:
图例.13. etdlines EA 的操作示例
EA 在长线行情下交易良好是显而易见的, 不过, 犬牙交错的行情下会产生一定数量的假信号并导致交易无利可图。EA 的测试结果如下:
图例.14. etdlines 的测试结果
结论
我的目标是描述由 Thomas DeMark 开发的 TD 点和 TD 线, 并以 MQL4 语言将其实现。本文提供了创建 3 个指标和 2 个 EA 的示例。我们可以看到, DeMark 的思路在逻辑上内嵌到交易系统, 其用例打开了巨大前景。