量化交易软件:海龟汤和海龟汤升级版的改进

本文介绍了如何使用MQL5实现《华尔街智慧》一书中描述的‘海龟汤’短线交易策略,并进行效能评估。作者通过结构而非类来实现面向对象编程,详细阐述了策略的规则和代码实现,包括设置挂单、止损和跟踪止损的方法。同时,还提到了策略的改进版‘海龟汤升级版’,并讨论了处理时间序列数据时的错误检查和记录级别。
摘要由CSDN通过智能技术生成

简介

《华尔街智慧:高胜算短线交易策略》一书的作者,劳伦斯.康纳斯和琳达.瑞斯克是有着34年交易经验的成功交易者,他们的经验包括股票交易,以及在银行、对冲基金、经济公司和咨询公司的职位。他们相信,您只需要一个交易策略就能够做到稳定获利地交易。但是,书中还是包含了20种左右不同的交易策略,分成四组,每组针对不同的市场周期,并且运用于一种稳定的价格行为模式。

在书中描述的策略非常流行,但是有必要知道的是,作者是基于15年到20年的市场行为来开发它们的。所以,本文有两个目标 — 赫兹量化将使用 MQL5 实现书中描述的第一个交易策略,然后我们将尝试使用赫兹量化策略测试器来评估它的效能,我们将使用 MetaQuotes 模拟服务器上近些年的价格历史。

当写代码时,我将假定MQL5的用户有基本的语言知识,也就是稍微高级些的初学者。所以,本文不包括对标准函数如何工作的解释,为什么使用这些类型的变量,这些细节应该是用户在编写EA交易之前在学习和练习中做的。另一方面,我也将不会考虑很有经验的EA交易开发人员,因为在实现新的交易策略时,他们已经有了测试好的,他们自己方案的开发库。

本文所面向的大多数编程人员都会对学习面向对象编程感兴趣,所以我将尝试使EA的开发过程对上述的目标有作用。为了使从过程到面向对象方法的迁移更加简单,赫兹量化不会使用面向对象编程中的最复杂部分 - 类,我们将会使用它们的简单类比 - 结构来替代。结构可以从逻辑上把不同类型的数据和用于操作它们的函数综合到一起,它们几乎拥有所有类的特性,包括继承。但是您可以在不知道类代码格式规则的基础上使用它们,您可以像您在过程式编程中一样做一些细小改动。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

‘海龟汤’交易系统与‘海龟汤升级版’的改进

海龟汤是在称为‘测试(Tests)’的系列交易策略中的第一个。说得更清楚一些,选择这个系列的基础是,它是根据‘使用价格来测试范围的边界或者支撑/阻力水平’。海龟汤是假定价格是不会不经一次反弹就突破20天的范围这一原则的,赫兹量化的任务就是从临时的反弹或者假突破中获利。 交易仓位的方向总是朝向通道内部,所以这交易策略可以称为一种“反弹策略”。 另外,海龟汤这个名称与著名的海龟策略类似,这不是偶然的 - 这两种策略都监视着20天范围内的价格行为。该书的作者已经尝试使用多种突破策略,包含了“海龟”,但是这样的交易还是不够有效,因为有许多假信号和深度的回撤。但是他们发现了一些模式,在它们的帮助下可以创建一系列原则来从与突破反方向的价格运动中获利。 在“海龟汤”交易策略中,一套完整的买入交易进场原则可以分析如下:

  1. 确认距离前一个20天低点至少过去了3天

  2. 等待资产价格跌破20天低点

  3. 在向下突破的价格低点上方5到10个点设置买入挂单

  4. 当挂单触发时,把止损设于当日最低价下方一个点的位置

  5. 当仓位有利润后使用跟踪止损

  6. 如果仓位在第一天或者第二天由止损关闭,您可以在初始水平重复进场

卖出交易规则是类似的,它们应用于范围的上方边界,也就是基于20天高点。

在代码库中有一个指标可以在历史柱上根据适当的设置显示通道的边界,在人工交易中您可以使用这个指标用于显示通道。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

交易策略的描述没有回答这样的问题:挂单应该保持多久,所以让我们使用一个简单的逻辑。当测试范围边界时,价格将会创建出新的极值点,所以后面一天上面的第一个条件就将不可能达到。因为那一天将不会有信号,我们将必须取消前一天的挂单。

这个交易策略的修改版,称为‘海龟汤升级版’的有两点差别:

  1. 不是在突破20天范围后立即设置挂单,而是等待一个确认信号 - 当天的柱收盘于范围之外,当日收盘在分析所得的水平通道边界之外也是可以的。

  2. 为了确定初始止损水平,赫兹量化使用两天的极值(最高或者最低价)水平。

定义通道参数

为了检验条件,赫兹量化需要知道范围的最高价和最低价,在定义了时间限制后就可以得到,在任意指定时间的通道中都是有四个变量决定了通道,所以它们可以组成一个结构。让我们在其中再加入交易策略中使用的两个变量,就是距离范围中最高价和最低价过去的天数(柱数):

 
 

struct CHANNEL { double d_High; // 范围上方边界的价格 double d_Low; // 范围下方边界的价格 datetime t_From; // 通道中第一个(最早的)柱的日期/时间 datetime t_To; // 通道中最后一个柱的日期/时间 int i_Highest_Offset; // 最高价右边的柱数 int i_Lowest_Offset; // 最低价右边的柱数 };

所有这些变量都将由f_Set函数来进行及时更新,该函数需要知道它应该从哪个柱开始绘制虚拟通道(i_Newest_Bar_Shift)以及它所应查看的历史深度 (i_Bars_Limit):

 
 

void f_Set(int i_Bars_Limit, int i_Newest_Bar_Shift = 1) { double da_Price_Array[]; // 用于保存通道中所有柱的最高/最低价格的辅助数组 // 确定通道的上方边界: int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array); int i_Bar = ArrayMaximum(da_Price_Array); d_High = da_Price_Array[i_Bar]; // 确定通道的上方边界 i_Highest_Offset = i_Price_Bars - i_Bar; // 距离最高价的时间(柱数) // 确定范围的最低边界: i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array); i_Bar = ArrayMinimum(da_Price_Array); d_Low = da_Price_Array[i_Bar]; // 确定通道的最低边界 i_Lowest_Offset = i_Price_Bars - i_Bar; // 距离最低点的时间(柱数) datetime ta_Time_Array[]; i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array); t_From = ta_Time_Array[0]; t_To = ta_Time_Array[i_Price_Bars - 1]; }

这个函数的代码只有13行,但是如果您已经在语言参考部分读过 MQL 函数对时间序列数据的读取(CopyHigh, CopyLow, CopyTime 以及其他), 您就知道它们其实没那么简单。有的时候,返回数值的数量和您请求的可能不同,因为请求的数据在您第一次访问想要的时间序列时可能还没有准备好。只有您正确处理结果,从时间序列中复制数据才能正常工作。

所以赫兹量化要满足确保编程质量的最低标准,加上一些错误处理。为了使错误更加容易理解,让我们把错误数据打印到记录中,记录在调试中也是很有用的,因为它可以含有为什么决定下单的详细信息。让我们引入一个枚举类型的新的变量,它将能设置我们的记录中应该包含多少详细信息。

 
 

enum ENUM_LOG_LEVEL { // 记录级别 LOG_LEVEL_NONE, // 禁用记录 LOG_LEVEL_ERR, // 只含有错误信息 LOG_LEVEL_INFO, // 错误 + EA的注释 LOG_LEVEL_DEBUG // 所有内容 };

所需的级别将由用户选择,将在许多函数中加入打印信息的处理。所以枚举列表和自定义变量Log_Level都应该在主程序的开始包含进来,而不是独立的一块。

让我们回到f_Set函数, 它将包含所有另外的检查了 (增加的代码行已经突出显示):

void f_Set(int i_Bars_Limit, int i_Newest_Bar_Shift = 1) { double da_Price_Array[]; // 用于保存通道中所有柱的最高/最低价格的辅助数组 // 确定通道的上方边界: int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array); if(i_Price_Bars == WRONG_VALUE) { // 处理 CopyHigh 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 错误 #%u", __FUNCSIG__, _LastError); return; } if(i_Price_Bars < i_Bars_Limit) { // CopyHigh 函数未能接收到请求数量的数据 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 已经复制了 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit); return; } int i_Bar = ArrayMaximum(da_Price_Array); if(i_Bar == WRONG_VALUE) { // 处理 ArrayMaximum 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMaximum: 错误 #%u", __FUNCSIG__, _LastError); return; } d_High = da_Price_Array[i_Bar]; // 确定通道的上方边界 i_Highest_Offset = i_Price_Bars - i_Bar; // 距离最高价的时间(柱数) // 确定范围的最低边界: i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array); if(i_Price_Bars == WRONG_VALUE) { // 处理 CopyLow 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 错误 #%u", __FUNCSIG__, _LastError); return; } if(i_Price_Bars < i_Bars_Limit) { // CopyLow 函数未能收到所请求数量的数据 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 已复制 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit); return; } i_Bar = ArrayMinimum(da_Price_Array); if(i_Bar == WRONG_VALUE) { // 处理 ArrayMinimum 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMinimum: 错误 #%u", __FUNCSIG__, _LastError); return; } d_Low = da_Price_Array[i_Bar]; // 确定通道的最低边界 i_Lowest_Offset = i_Price_Bars - i_Bar; // 距离最低点的时间(柱数) datetime ta_Time_Array[]; i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array); if(i_Price_Bars < 1) t_From = t_To = 0; else { t_From = ta_Time_Array[0]; t_To = ta_Time_Array[i_Price_Bars - 1]; } } 当发现错误时,赫兹量化做以下事情: 中断执行,这样终端可以下载所需的数据,直到下个订单时刻再来复制所需的数据。为了防止直到过程完全结束前有其他函数使用通道,让我们在结构中加入相应的标志 b_Ready (true = 数据已经准备好, false = 过程还没有完成)。我们还将加入通道更新的标志 (b_Updated) — 为了更好的效率, 它对了解交易策略中四个参数是否有所变化有用。为此我们需要再加入一个变量 - 通道的签名 (s_Signature)。f_Set 函数也要加到结构中,而 CHANNEL 结构看起来就像这样: // 用于在一个结构中收集和更新通道的信息和函数 struct CHANNEL { // 变量 double d_High; // 范围上方边界的价格 double d_Low; // 范围下方边界的价格 datetime t_From; // 通道的第一个(最前面的)柱的日期/时间 datetime t_To; // 通道中最后一个柱的日期/时间 int i_Highest_Offset; // 最高价右边的柱数 int i_Lowest_Offset; // 最低价右边的柱数 bool b_Ready; // 参数更新过程是否结束了? bool b_Updated; // 通道参数改变了吗? string s_Signature; // 最后数据的签名 // 函数: CHANNEL() { d_High = d_Low = 0; t_From = t_To = 0; b_Ready = b_Updated = false; s_Signature = "-"; i_Highest_Offset = i_Lowest_Offset = WRONG_VALUE; } void f_Set(int i_Bars_Limit, int i_Newest_Bar_Shift = 1) { b_Ready = false; // Pitstop: 设置一个服务标志 double da_Price_Array[]; // 用于保存通道中所有柱的最高/最低价格的辅助数组 // 确定通道的上方边界: int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array); if(i_Price_Bars == WRONG_VALUE) { // 处理 CopyHigh 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 错误 #%u", __FUNCSIG__, _LastError); return; } if(i_Price_Bars < i_Bars_Limit) { // CopyHigh 函数未能接收到请求数量的数据 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: 已经复制了 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit); return; } int i_Bar = ArrayMaximum(da_Price_Array); if(i_Bar == WRONG_VALUE) { // 处理 ArrayMaximum 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMaximum: 错误 #%u", __FUNCSIG__, _LastError); return; } d_High = da_Price_Array[i_Bar]; // 确定通道的上方边界 i_Highest_Offset = i_Price_Bars - i_Bar; // 距离最高价的时间(柱数) // 确定范围的最低边界: i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array); if(i_Price_Bars == WRONG_VALUE) { // 处理 CopyLow 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 错误 #%u", __FUNCSIG__, _LastError); return; } if(i_Price_Bars < i_Bars_Limit) { // CopyLow 函数未能收到所请求数量的数据 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: 已复制 %u 个柱,共需 %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit); return; } i_Bar = ArrayMinimum(da_Price_Array); if(i_Bar == WRONG_VALUE) { // 处理 ArrayMinimum 函数的错误 if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMinimum: 错误 #%u", __FUNCSIG__, _LastError); return; } d_Low = da_Price_Array[i_Bar]; // 确定通道的最低边界 i_Lowest_Offset = i_Price_Bars - i_Bar; // 距离最低点的时间(柱数) datetime ta_Time_Array[]; i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array); if(i_Price_Bars < 1) t_From = t_To = 0; else { t_From = ta_Time_Array[0]; t_To = ta_Time_Array[i_Price_Bars - 1]; } string s_New_Signature = StringFormat("%.5f%.5f%u%u", d_Low, d_High, t_From, t_To); if(s_Signature != s_New_Signature) { // 通道数据已经改变 b_Updated = true; if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 通道已更新: %s .. %s / %s .. %s, min: %u max: %u ", __FUNCTION__, DoubleToString(d_Low, _Digits), DoubleToString(d_High, _Digits), TimeToString(t_From, TIME_DATE|TIME_MINUTES), TimeToString(t_To, TIME_DATE|TIME_MINUTES), i_Lowest_Offset, i_Highest_Offset); s_Signature = s_New_Signature; } b_Ready = true; // 数据更新成功完成 } };

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值