依据 Heiken-Ashi 指标的交易系统示例
简介
二十多年前烛形图在美国出现之后,对牛熊如何在西方市场中角力的理解出现了一场革命。烛形图成为一种流行的交易工具,交易者开始使用它们,从而让对图表的理解更加容易。但是对烛形图的判读因人而异。
这些方法中的一种改变传统的烛形图,有利于对其的理解,称为 Heikin Ashi 技术。
1. «Nani Desu Ka?»*
有关此主题的第一篇文章出于在 2004 年二月期的《股票与商品技术分析》杂志中,在该期杂志中 Dan Valcu 发表了一篇名为《使用 Heikin Ashi 技术》的文章(到原文的链接)
在他的网站上,作者指出在 2003 年夏天,他研究了 Ichimoku 技术,并且因为经常出现,意外地发现有一些图展现出清晰可见的市场趋势。这演变为 Heikin-Ashi 图,是更为精确、有一定变化的烛形图。
这种分析方法是一名日本人发明的,到目前为止,他用此方法变得非常成功。让作者感到惊讶的是,他在书箱或互联网中找不到其他相关信息,因此决定通过在杂志中发表该方法来让所有交易者都能运用此方法。
Heikin-Ashi 方法(heikin 在日语中表示“中间”或“平衡”,ashi 表示“脚”或“柱”)是用于评估趋势及其方向与强度的可视化工具。这不是交易的“圣杯”,但它是一个非常优秀且易于使用的可视化趋势工具。
让我们考虑一下 OHLC 烛形图数值的计算是如何进行的:
当前柱的收盘价:haClose = (Open + High + Low + Close) / 4
当前柱的开盘价:haOpen = (haOpen [before.]+ HaClose [before]) / 2
当前柱的最高价:haHigh = Max (High, haOpen, haClose)
当前柱的最低价:haLow = Min (Low, haOpen, haClose)
"Open"、"High"、"Low" 和 "Close" 的值都是指与当前柱有关的值。前缀 "ha" 表示 heikin-ashi 的对应修改值。
为了便于理解市场信息,Heikin-Ashi 技术通过创建所谓的合成烛形来修改传统的烛形图,从普通烛形图中去除不规则的烛,从而提供更好的趋势与平台整理图形。看一看用此方法创建的烛形图,您就可以对市场及其风格一目了然:
图 1. 左侧是常规烛形图 (a),右侧是 Heikin-Ash 烛形图 (b)
图 1 显示了传统日本烛形图与 Heiken Ashi 烛形图之间的区别。这些图形的明显特征是在向上趋势中,大多数的白色烛没有影线。在下降趋势中,大多数的黑色烛没有上影线。Heiken Ashi 烛形图没有缺口,因此新烛在上一烛的中间水平开盘。
Heiken-Ashi 烛形图上的烛显示了比传统烛形图更广泛的趋势指示。当趋势减弱时,烛的主体变短,影线变长。烛颜色的改变是买/卖的信号。依据这些图表确定纠正移动的结束是最方便的。
此指标是 MetaTrader 5 的一部分,您可以在文件夹 Indicators \\ Examples \\ Heiken_Ashi.mq5 中找到它。在将指标安装到图表之前,我建议使图形线性化。同样,在图形的属性中,在 "General"(常规)选项卡中,取消选中 "from the top graph"(从顶部图形)一项。
我想再一次将您的注意力聚焦到 Heiken-Ashi 方法不是“圣杯”这一事实上。为了证明这一点,我将尝试仅使用这一技术创建一个简单的交易系统 (TS)。
为此,我们需要使用 MQL5 编程语言和标准库中的类创建一个简单的 EA 交易程序,然后使用 MetaTrader 5 客户端的策略测试程序用历史数据对其进行测试。
2. 交易系统算法
不需要太复杂,我们使用 Dan Valcu 在以下网站中提出的 Heiken-Ashi 程序的六个基本原则创建算法:http://www.educofin.com/
- 上升趋势 - 蓝色烛 haClose> haOpen
- 下降趋势 - 红色烛 haClose <haOpen
- 强的上升趋势 - 蓝色烛,其中没有 Low haOpen == haLow
- 强的下降趋势 - 红色烛,其中没有 High haOpen == haHigh
- 平台整理 - 一系列具有短主体(任意颜色)和长影线的烛
- 趋势改变 - 具有相反颜色的短主体和长影线的烛。它并不始终都是一个可靠的信号,有时可能仅是平台整理 (5) 的一部分。
(1,2) 趋势容易理解 - 如果在交易之中,我们只需要保持仓位,将止损移到低于/高于上一烛的 1-2 个点即可。
对于强的趋势 (3,4),我们以相同的方式操作 - 增大止损。
对于平台整理 (5) 和趋势改变 (6),平仓(如果没有通过止损平仓的话),然后我们需要决定是否建立一个相反的仓位。为了做出决定,我们需要以某种方式确定是出现平台整理还是出现反向。我们需要一个依据指标、烛形图分析或图形分析建立的过滤器。
本文的目标不包括建立一个能够盈利的策略,但是谁知道作为一个结果我们会实现什么呢?因此,让我们考虑以下相反颜色的烛的出现,我们将平仓并以相反方向建立新仓。
那么,我们的算法如下所示:
- 在相反颜色的烛形成之后,我们平以前的仓位(如果有的话),并且在新烛开盘时建仓,将止损设置为低于/高于上一烛的最低价/最高价 2 个点。
- 趋势 - 我们将止损移到低于/高于上一烛的最低价/最高价 2 个点。
- 对于强的趋势,我们采取与趋势相同的步骤,即移动止损。
整体而言,所有事情都很简单,并且对读者也可能很清晰。现在,我们将用 MQL5 语言实现这一目的。
3. 用 MQL5 编写 EA 交易程序
要创建 EA 交易程序,我们仅需要一个输入参数 - 手数、两个事件处理函数 OnInit ()、OnTick (),和我们自己的函数 CheckForOpenClose ()。
要用 MQL5 设置输入参数,我们使用 Input 变量。
//--- 输入参数 input double Lot=0.1; // 交易量大小
函数 OnInit () 是事件处理程序 Init。Init 事件在加载 EA 交易程序之后立即生成。
在这个函数的代码里,我们会将指标连接到 EA 交易程序。如我在前文所述,标准 MetaTrader 5 包含一个 Heiken_Ashi.mq5 指标。
如果我们有用于计算指标的公式,并且我们能够在 EA 交易程序的代码中计算值,也许您会疑惑为什么还要这么复杂?是的,我承认,是可以这样做,但是如果您仔细看一看它们:
haOpen=(haOpen[prev.]+haClose[prev])/2
您将看到它使用以前的值,这样会对独立计算造成一定的不便,使我们的生活变得复杂。因此,代替独立计算,我们将开发 MQL5 连接我们的自定义指标的能力,即函数 iCustom。
为此,我们向函数 OnInit () 的主体添加以下代码行:
hHeiken_Ashi=iCustom(NULL,PERIOD_CURRENT,"Examples\\Heiken_Ashi");
并且我们获得一个全局变量 hHeiken_Ashi - Heiken_Ashi.mq5 指标的句柄,我们在将来会需要该变量 。
函数 OnTick () 是 NewTick () 事件的处理程序,该事件在新的价格变动出现时发生。
//+------------------------------------------------------------------+ //| EA订单函数 | //+------------------------------------------------------------------+ void OnTick() { //--- 检查能否交易以及计算的柱数 if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) if(BarsCalculated(hHeiken_Ashi)>100) { CheckForOpenClose(); } //--- }
函数 TerminalInfoInteger (TERMINAL_TRADE_ALLOWED) 检查是否允许交易。使用函数 BarsCalculated (HHeiken_Ashi),我们检查所请求指标(在我们的例子中为 Heiken_Ashi.mq5)的计算数据的量。
如果两个条件都满足,我们将看到函数 CheckForOpenClose () 的执行,主要工作就在该函数中进行。让我们更加仔细地看一看。
因为我们的交易系统条款指定订单的成交发生在新烛开盘之时,我们需要确定新烛是否已经开盘。有很多方式可以实现此目的,但是最简单的方式是检查价格变动的量。因此,如果价格变动量等于 1,则表示新柱的开盘,您应检查交易系统的条款并发布订单。
我们按以下方式实施:
//--- 只有在收到新烛形的第一笔订单号时处理订单 MqlRates rt[1]; if(CopyRates(_Symbol,_Period,0,1,rt)!=1) { Print("CopyRates ",_Symbol," 失败,没有历史"); return; } if(rt[0].tick_volume>1) return;
创建一个类型为 MqlRates、大小为一个元素的变量数组。使用函数 CopyRates () 将最后一根柱的值加入该变量数组。然后检查价格变动量,并且在变动量大于 1 时终止函数,否则继续进行计算。
接下来,使用指令 #define 声明几个记忆常量:
//--- 为了检查条件我们需要最后3个柱 #define BAR_COUNT 3 //--- 用于保存开盘价的指标缓冲区编号 #define HA_OPEN 0 //--- 用于保存最高价的指标缓冲区编号 #define HA_HIGH 1 //--- 用于保存最低价的指标缓冲区编号 #define HA_LOW 2 //--- 用于保存收盘价的指标缓冲区编号 #define HA_CLOSE 3
再然后,我们声明数组:
double haOpen[BAR_COUNT],haHigh[BAR_COUNT],haLow[BAR_COUNT],haClose[BAR_COUNT];
并且使用函数 CopyBuffer () 获取相应数组中的指标值。
if(CopyBuffer(hHeiken_Ashi,HA_OPEN,0,BAR_COUNT,haOpen)!=BAR_COUNT || CopyBuffer(hHeiken_Ashi,HA_HIGH,0,BAR_COUNT,haHigh)!=BAR_COUNT || CopyBuffer(hHeiken_Ashi,HA_LOW,0,BAR_COUNT,haLow)!=BAR_COUNT || CopyBuffer(hHeiken_Ashi,HA_CLOSE,0,BAR_COUNT,haClose)!=BAR_COUNT) { Print("从 Heiken_Ashi CopyBuffer 失败, 没有数据"); return; }
我想将您的注意力聚焦到数据是如何存储在变量数组中的。
“最旧的”(历史顺序)柱存储在数组的第一个元素(零)中。
“最新的”(当前)柱存储在后面,BAR_COUNT-1(图 2)。
图 2. 烛的顺序和数组索引的值
因此,我们已经获得了 OHLC Heiken-Ashi 值,剩下的是验证建仓或持仓的条件。
详细考虑买入信号的处理。
如我在前面所指出,我们获得了三个 Heiken-Ashi 烛的值。当前值位于编号 [BAR_COUNT-1 = 2] 的单元格中,并且对我们而言并不是必不可少的。先前的值位于单元格 [BAR_COUNT-2 = 1] 中,更早的烛位于 [BAR_COUNT-3 = 0] 中(见图 2),并且依据这两根烛,我们将检查进行交易的条款与条件。
然后,我们需要检查工具上的未平仓位。为此,我们将使用默认库中交易类的 CPositionInfo 类。此类允许我们获得有关未平仓位的信息。我们使用方法 Select (_Symbol) 确定在我们的工具上是否存在未平仓位,如果存在,则使用方法 Type () 确定未平仓位的类型。
如果目前我们有要买入的未平仓位,则我们需要平仓。
为此,我们使用标准类库中专为执行交易操作而设计的 CTrade 类的方法。
我们将使用方法 PositionClose (const string symbol, ulong deviation) 来平仓,其中 symbol 是工具的名称,第二个参数 deviation 是平仓价的允许偏差。
然后,我们依据我们的交易系统检查烛形图组合。因为我们已经检查了新形成的烛(索引为 [BAR_COUNT-2]),我们仅需要检查它前面的烛(索引为 [BAR_COUNT-3]),并且执行建仓所需的步骤。
//--- 检查是否有持仓,如果有,则平掉 if(posinf.Select(_Symbol)) { if(posinf.Type()==POSITION_TYPE_BUY) { // lot=lot*2; trade.PositionClose(_Symbol,3); } } //--- 检查和设置止损水平 double stop_loss=NormalizeDouble(haHigh[BAR_COUNT-2],_Digits)+_Point*2; double stop_level=SymbolInfoDouble(_Symbol,SYMBOL_ASK)+SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point; if(stop_loss<stop_level) stop_loss=stop_level; //--- 检查组合: 相反颜色的烛形已经生成 if(haOpen[BAR_COUNT-3]<haClose[BAR_COUNT-3]) { if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,SymbolInfoDouble(_Symbol,SYMBOL_BID),stop_loss,0)) Print(trade.ResultRetcodeDescription()); } else if(posinf.Select(_Symbol)) { if(!trade.PositionModify(_Symbol,stop_loss,0)) Print(trade.ResultRetcodeDescription()); }
在这里,必须将您的注意力转到 CTrade 类的三种方法的使用:
- 方法 PositionOpen (symbol, order_type, volume, price, sl, tp, comment) 用于建仓,其中 symbol 是工具的名称,order_type - 订单类型,volume - 手数,price - 买入价,sl - 止损,tp - 盈利,comment - 备注。
- 方法 PositionModify (symbol, sl, tp) 用于更改止损和盈利的值,其中,symbol - 工具的名称,sl - 止损,tp - 盈利。我想提醒您,在使用此方法之前,您应检查是否存在未平仓位。
- 方法 ResultRetcodeDescription () 用于获取代码错误的说明,采用行的形式。
在计算变量 stop_loss 的过程中,haHigh [BAR_COUNT-2] 的值是一个计算,来自指标,并且需要通过函数 NormalizeDouble (haHigh [BAR_COUNT-2], _Digits) 进行规范化,以便正确使用。
这样就完成了卖出信号的处理。
要买入,我们使用相同的原理。
以下是完整的 EA 交易程序的代码:
//+------------------------------------------------------------------+ //| Heiken_Ashi_Expert.mq5 | //| Copyright VDV Soft | //| vdv_2001@mail.ru | //+------------------------------------------------------------------+ #property copyright "VDV Soft" #property link "vdv_2001@mail.ru" #property version "1.00" #include <Trade\AccountInfo.mqh> #include <Trade\PositionInfo.mqh> #include <Trade\Trade.mqh> //--- 全局变量列表 //--- 输入参数 input double Lot=0.1; // 手数大小 //--- 指标句柄 int hHeiken_Ashi; //+------------------------------------------------------------------+ //| EA初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- hHeiken_Ashi=iCustom(NULL,PERIOD_CURRENT,"Examples\\Heiken_Ashi"); //--- return(0); } //+------------------------------------------------------------------+ //| EA订单函数 | //+------------------------------------------------------------------+ void OnTick() { //--- 应该允许交易并且计算的柱数>100 if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) if(BarsCalculated(hHeiken_Ashi)>100) { CheckForOpenClose(); } //--- } //+------------------------------------------------------------------+ //| 检查建仓条件 | //+------------------------------------------------------------------+ void CheckForOpenClose() { //--- 只在新柱产生时处理订单 MqlRates rt[1]; if(CopyRates(_Symbol,_Period,0,1,rt)!=1) { Print("CopyRates ",_Symbol," 失败,没有历史"); return; } if(rt[0].tick_volume>1) return; //--- 为了检查条件我们需要最后3个柱 #define BAR_COUNT 3 //--- 保存开盘价的指标缓冲区索引 #define HA_OPEN 0 //--- 保存最高价的指标缓冲区索引 #define HA_HIGH 1 //--- 保存最低价的指标缓冲区索引 #define HA_LOW 2 //--- 保存收盘价的指标缓冲区索引 #define HA_CLOSE 3 double haOpen[BAR_COUNT],haHigh[BAR_COUNT],haLow[BAR_COUNT],haClose[BAR_COUNT]; if(CopyBuffer(hHeiken_Ashi,HA_OPEN,0,BAR_COUNT,haOpen)!=BAR_COUNT || CopyBuffer(hHeiken_Ashi,HA_HIGH,0,BAR_COUNT,haHigh)!=BAR_COUNT || CopyBuffer(hHeiken_Ashi,HA_LOW,0,BAR_COUNT,haLow)!=BAR_COUNT || CopyBuffer(hHeiken_Ashi,HA_CLOSE,0,BAR_COUNT,haClose)!=BAR_COUNT) { Print("从 Heiken_Ashi CopyBuffer 失败, 没有数据"); return; } //---- 检查卖出信号 if(haOpen[BAR_COUNT-2]>haClose[BAR_COUNT-2])// bear candlestick { CPositionInfo posinf; CTrade trade; double lot=Lot; //--- 检查是否有持仓,如果有,则平掉 if(posinf.Select(_Symbol)) { if(posinf.Type()==POSITION_TYPE_BUY) { // lot=lot*2; trade.PositionClose(_Symbol,3); } } //--- 检查和设置止损水平 double stop_loss=NormalizeDouble(haHigh[BAR_COUNT-2],_Digits)+_Point*2; double stop_level=SymbolInfoDouble(_Symbol,SYMBOL_ASK)+SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point; if(stop_loss<stop_level) stop_loss=stop_level; //--- 检查组合: 相反颜色的烛形已经生成 if(haOpen[BAR_COUNT-3]<haClose[BAR_COUNT-3]) { if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,SymbolInfoDouble(_Symbol,SYMBOL_BID),stop_loss,0)) Print(trade.ResultRetcodeDescription()); } else if(posinf.Select(_Symbol)) { if(!trade.PositionModify(_Symbol,stop_loss,0)) Print(trade.ResultRetcodeDescription()); } } //---- 检查买入信号 if(haOpen[BAR_COUNT-2]<haClose[BAR_COUNT-2]) // bull candle { CPositionInfo posinf; CTrade trade; double lot=Lot; //--- 检查是否有持仓,如果有,则平掉 if(posinf.Select(_Symbol)) { if(posinf.Type()==POSITION_TYPE_SELL) { // lot=lot*2; trade.PositionClose(_Symbol,3); } } //--- 检查和设置止损水平 double stop_loss=NormalizeDouble(haLow[BAR_COUNT-2],_Digits)-_Point*2; double stop_level=SymbolInfoDouble(_Symbol,SYMBOL_BID)-SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point; if(stop_loss>stop_level) stop_loss=stop_level; //--- 检查组合: 相反颜色的烛形已经生成 if(haOpen[BAR_COUNT-3]>haClose[BAR_COUNT-3]) { if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,SymbolInfoDouble(_Symbol,SYMBOL_ASK),stop_loss,0)) Print(trade.ResultRetcodeDescription()); } else if(posinf.Select(_Symbol)) { if(!trade.PositionModify(_Symbol,stop_loss,0)) Print(trade.ResultRetcodeDescription()); } } } //+------------------------------------------------------------------+
可以在附带的文件 Heiken_Ashi_Expert.mq5 中找到完整的 EA 交易程序的文本。将其复制到目录 ..\ MQL5 \ Experts,然后通过菜单“Tools(工具) -> Editor MetaQuotes Language(MetaQuotes 语言编辑器)”运行 MetaEditor,或使用 F4 键。接下来,在 "Navigator"(导航器)窗口中,打开选项卡"Experts"(EA),然后通过双击,将文件 Heiken_Ashi_Expert.mq5 下载到编辑窗口,并通过按 F7 来进行编译。
如果正确执行了所有操作,则将在 "Navigator"(导航器)窗口中的选项卡 "Expert Advisors"(EA)中创建文件 Heiken_Ashi_Expert。必须以相同的方式编译 Heiken_Ashi.mq5 指标,它文件位于目录 \ MQL5 \ Indicators \ Examples \ 中。
4. 用历史数据测试交易系统
要检查我们的交易系统的生命力,我们将使用 MetaTrader 5 策略测试程序,该程序是交易平台的一部分。测试程序通过客户端菜单“View(视图) -> Strategy Tester(策略测试程序)” 或按组合键 Ctrl + R 来运行。一旦启动,我们将前往 "Settings"(设置)选项卡(图 3)。
图 3. 策略测试程序的设置
配置 EA 交易程序 - 从我们的 EA 交易程序列表中选择,指定测试间隔为 2000 年初至 2009 年尾,初始存款为 10,000 美元,禁用优化(因为我们只有一个输入参数,并且我们只想检查交易系统的生命力)。
将使用两个货币对进行测试。我决定选择货币对 EURUSD 和 GBPUSD。
对于测试,我决定采用以下时间间隔:H3、H6 和 H12。您将问为什么?答案是因为我想测试在时间间隔内的交易系统,而在 MetaTrader4 客户端中不存在这样的时间间隔。
因此我们这样选择。我们选择测试货币 EURUSD,测试周期 H3,然后单击 "Start"(开始)。在测试完成时,我们在测试程序窗口中看到两个新的选项卡:"Results"(结果,图 4) 和 "Graph"(图形,图 5)。
图 4. 策略测试 EURUSD H3 的结果
您可以从测试结果(图 4)看到,从 2000 年早期到 2009 年末期,按照指定参数,交易系统产生 2560.60 美元的损失。
图形(图 5)显示按一段时间内盈利和损失的分布,这使我们有机会复核交易系统在指定时间内的性能,以及分析系统错误。
图 5. 策略测试程序的 "Graph" (图形)选项卡 (EURUSD H3)
我几乎忘记了 "Results"(结果)选项卡在默认情况下创建一个简单的报告。此外,我们能够查看交易、订单和写好的文件报告。
为此,我们只需要将光标移到选项卡上,再单击鼠标右键,然后选择相应的菜单项:
图 6. 策略测试程序的 Results(结果)选项卡的上下文菜单
以下是在六小时周期 (H6) 上的测试结果:
图 7. 策略测试程序的 Results(结果)选项卡 (EURUSD H6)
在 12 小时周期上 (H12)。
图 8. 策略测试程序的 Results(结果)选项卡 (EURUSD H12)
似乎在 EURUSD 等货币对上,我们的策略无效。但是我们可以注意到,工作周期的变化显著影响结果。
我们将测试延伸到货币对 GBPUSD,以做出关于我们的交易系统的效能的最终结论。
图 9. 策略测试程序的 Results(结果)选项卡 (GBPUSD H3)
图 10. 策略测试程序的 Results(结果)选项卡 (GBPUSD H6)
图 11. 策略测试程序的 Results(结果)选项卡 (GBPUSD H12)
图 12. 策略程序的 Graph(图形)选项卡 (GBPUSD H12)
在分析测试结果之后,我们看到使用 GBPUSD 等货币对,我们的系统在两种分开的情形中证明了盈利结果。在 12 小时周期内,我们获得了相当可观的利润 8903.23 美元,尽管是通过 9 年的时间获得的。
那些有兴趣的人可以测试其他货币对。我的假设是货币对越不稳定,则获得的结果越好,并且应使用更长的周期。
总结
在总结中,我强调此交易系统并不是“圣杯”,并且不能单独使用。
然后,如果与其他信号(烛形图分析、波浪分析、指标、趋势)一起使用,我们从平台整理信号中分离出反转信号,然后在某些不稳定的交易工具上,它可能会非常有生命力,尽管不太可能带来“疯狂”的利润。
__________________________________
* "Nani Desu Ka?" - 这是什么?(日语)
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/91