这是一篇有点长的文章,如果你有耐心看完,我想你一定会是一个能在交易市场上呼风唤雨的好汉。
海龟的起源
海龟交易的一切都源于全世界最著名的期货投机家理查德 丹尼斯和他的好朋友比尔 埃克哈特的一场争论,就是杰出的交易者究竟是天生的还是培养出来的。丹尼斯相信他几乎可以把任何一个人变为优秀的交易者,埃克哈特则认为这是一种天赋问题,不是培养问题。丹尼斯愿意用自己的钱来证明自己的话,于是两人打了一个赌。
为此,他们在《华尔街日报》、《巴伦周刊》和《纽约时报》上刊登了大幅广告,宣布丹尼斯正在招募培训生,他会把自己的交易方法传授给这些人,然后给每个人一个100万美元的交易账户。这些培训生就被叫做海龟。
我认为海龟交易法则是一套投机理念,它的技术规则、操作规则与心理解析共同支撑了这套交易系统,而不是单单根据一些技术数据进行交易,它的技术数据可能在我们自己的市场得不到验证,但是整个理念适合于所有的投机市场。
《海龟交易法则》这本书的作者就是丹尼斯的门徒之一,他负责丹尼斯最大的一个账户,为丹尼斯赚了3100多万美元。
海龟的思维
三种类型的交易者
对冲者:一般为企业,通过买卖期货合约抵消期货价格波动风险。
帽客:经营市场的流动性风险,希望通过快速与对冲者交易赚取价差。他们为市场创造了流动性,因为他们在不停的出价、要价,寄希望于买单和卖单之间的平衡。
投机者:寄希望短期价格的预判。
价格变动取决于市场中所有的买者和卖者的共同态度,这些买卖者就是我们所说的那些帽客、投机者和对冲者:想在一天内反复赚取微小买卖价差的人(帽客),想投机于一天或几个星期、几个月内价格大幅变化的人(投机者),以及想规避经营风险的人。(对冲者)
共同态度变了,价格就会变化。不管什么原因,一旦卖者不再愿意以目前的价格卖出,而是想提高价格,买者又愿意接受这个更高的价格时,市场价格就会上涨。反之亦然。
交易过程中出现的情绪:
- 希望:我当然希望我买了之后,它马上就涨
- 恐惧:我再也赔不起了,这一次我得躲得远远的。
- 贪心:我赚翻了,我要把我的头寸扩大一倍。
- 绝望:这个交易系统不管用,我一直在赔钱。
在那些较为简单原始的环境中,人类已经形成的某些特定的世界观对他们大有帮助,但在交易世界中,这些认识反而成了障碍。人类认识现实的方式可能出现扭曲,科学家们称之为认知偏差。以下是几种对交易行为有影响的认知偏差:
1、损失厌恶:对避免损失有一种强烈的偏好。也就是说,不赔钱远比赚钱更重要。
患上损失厌恶症的人对避免损失有一种绝对的偏好,盈利只是第二位的。对大多数人来说,没有赚到100美元与损失了100美元并不是一回事。但是从理性角度来看,这两者是一回事:它们都代表着这100美元没有盈利。
2、沉没成本效应:更重视已经花掉的钱,而不是而来可能要花的钱。
假设ACME公司投入了1亿美元开发一种用于生产笔记本电脑显示屏的特殊技术,但在这笔钱已经花掉之后,该公司却发现另外一种技术明显更好,而且更有可能及时带来它所期望的成果。如果从纯理性的角度出发,ACME公司应该权衡一下采用这种新技术的未来成本和继续使用当前技术的未来成本,然后根据未来的收益和支出状况作出决策,完全不必考虑已经花掉的那些研发投资。
3、处置效应:早早兑现利润,却让损失持续下去。指投资者倾向于卖掉价格正在上涨的股票,却保留价值正在下跌的股票。有人认为这种效应与沉没成本效应有关。
4、结果偏好:只会根据一个决策的结果来判断它的好坏,而不去考虑决策本身的质量。
5、近期偏好:更重视近期的数据或经验,忽视早期的数据或经验。指一个人更重视近期的数据和经验。昨天的一笔交易比上个星期或上一年的交易重要。近两个月的赔钱经历可能跟过去6个月的赚钱经历同样重要,甚至更加重要。于是,近期的一连串不成功的交易会导致交易者怀疑他们的方法和决策程序。
6、锚定效应:过于依赖容易获得的信息。他们可能会盯着近期的一个价位,根据当前价格与这个参考价格的关系来作出决策。
7、潮流效应:盲目相信一件事,只因为其它许多人都相信它。
8、小数定律:从太少的信息中得出没有依据的结论。小样本对总体并没有太大的代表性。比如,如果一个交易系统在6次测试中有4次成功,大多数人都会说这是一个好系统,但从统计学上说,并没有足够多的信息可以支持这种结论。
一些交易风格
1、趋势跟踪
使用这种方法的交易者视图利用几个月内的大趋势。趋势跟踪者在市场处于历史高位或低位的时候入市,如果市场逆转并维持了几个星期,他们就会退出。
2、反趋势交易
与趋势交易相反,在价格接近新高的时候卖空。他们的理论依据是,新高的突破大多都不会引发市场趋势。
3、波段交易
本质上与趋势跟踪交易相同,只不过它瞄准的是短期市场动向。比如,一次成功的波段交易可能只会持续三四天,而不是几个月。
4、当日交易
一个真正的当日交易者总是试图在每天的交易结束之前退出市场。这样一来,即使夜间爆发的负面新闻引发了开盘之后的剧烈变化,他们的头寸也不会受到什么影响。
市场状态
讲技术的书,以下面的描述最为模棱两可,因为很多参数没有量化。比如第一个稳定平静的状态,较小的范围是多少就很难界定,而且各人有各人的理解。但是这个问题直接影响到进场的时机,所以又必须进行量化。我不认为所有讲述技术的书都是扯淡,也不认为所有讲述技术的书都是真实的,最重要的还是得到一种思路,然后通过思路进行实验,得出一个适合于自己的套路。这个适合于自己包括自己的风险承受能力,自己的资金量和操作手法。为了将下面描述的市场状态做一个量化,首先假定观察周期是3个月。
1、稳定平静
价格在一个相对较小的范围内上下波动,很少超出这个范围。
量化指标:如果观察周期内最高价与最低价的波幅不超过20%,以观察周期的第一天价格为基准价,周期最后一天价格与基准价格波幅不超过10%,则为稳定平静状态。
例子:鲁抗医药2019年9月24日-12月31日
2、稳定波动
有大的日间或周间变化,但没有重大的月际变化。
量化指标:如果观察周期内最高价与最低价的波幅在20%-50%之间,周期最后一天价格与基准价格波幅不超过20%,则为稳定波动状态。
例子:坚瑞沃能2020年4月17日-7月24日
3、平静的趋势
价格在几个月中呈现出缓慢的运动或趋向,但始终没有剧烈的回调或反方向运动。
量化指标:向上趋势为将周期分为均等的10段,每一段的波幅都不超过-20%,基准价向下波幅不超过-20%,周期最后一天价格幅度超过基准价60%,向下趋势正好相反
例子:神驰机电2020年1月23日-5月8日
4、波动的趋势
价格有大的变化,偶尔伴有剧烈的短期逆转。
量化指标:向上趋势为基准价向下幅度在-20%以下,周期最后一天价格幅度超过60%,向下趋势正好相反
例子:神驰机电2020年1月20日至4月30日
海龟的课程
1、破产风险
里面举了一个掷骰子的例子,一个骰子6个面,假如你掷出4点、5点或6点,你每押1美元就赔给你2美元,那么如果你口袋里有1000美元,你每次会押多少钱?是押1000美元、500美元还是100美元?从概率上来说,如果你一次就all in,那么你有50%的几率破产,如果你每次押500美元,那么也有25%的几率破产,如果你每次押100美元,那么你的破产概率是 =0.097%
破产风险会随着赌注的增加而不成比例地迅速增大,这是它最重要的特征之一。如果你把每次的赌金翻一番,破产风险一般不止翻一番,视系统特性的不同,风险有可能翻上两番、三番甚至四番。
2、资金管理
所谓资金管理,就是指控制市场风险的程度,确保交易者能安然度过每一个交易者都必然要碰到的不利时期。交易者既要让盈利潜力最大化,又要把破产风险控制在可以接受的水平,资金管理就是这样一门艺术。
这里书中的第三章看的不是太明白,所以查阅了知乎上的文章
这里复述下
2.1、首先要计算一个叫做ATR的技术指标,这个技术指标的算法如下:
2.1.1、当前交易日的最高价与最低价间的波幅
2.1.2、前一交易日收盘价与当前交易日最高价间的波幅
2.1.3、前一交易日收盘价与当前交易日最低价间的波幅
以上三个数取最大值就叫真实波幅,根据迭代算法,每天都可以取这么一个真实波幅,比如取14天真实波幅的平均值作为当天的ATR(平均真实波幅),其实就跟移动平均线的算法是一致的,只不过具体的规则有点区别。
举个例子:
假设初始资金为init元,让1个ATR波动表式账户的1%,再假设某股票价格为p,当前ATR值为atr,那么应该买多少股呢?
应该买的股数 = init * 1% / atr
那么交易的资金 = p * init * 1% / atr= init * 1% * p / atr
如果我们的止损定在1atr,那么当亏损 = -atr * init * 1% * p / atr = - init * 1% 的时候,就止损
如果我们的止损定在2atr,那么当亏损 = -2 * atr * init * 1% * p / atr = -2 * init * 1%的时候,就止损。
下面是原版海龟的ATR计算方法
原版海龟ATR的计算方法
真实波幅 = max(H-L, H-PDC, PDC-L)
H=当日最高价
L=当日最低价
PDC=前一日收盘价
N = (19 * PDN +TR)/20
PDN = 前一日的N值
TR= 当日的真实波动幅度
由于公式中需要前一日的N值,你在首次计算N的时候不能用这个公式,只能计算真实波动幅度的20日简单平均值。 资金管理是海龟交易的精髓所在。
3、期望值
运用概率学公式,E= ,如果在有限次交易中,最后E的结果为正,那么表示策略是可行的。所以短期的失利不重要,海龟以这样的方式看待损失:损失只是做生意的成本,并不代表着一次错误交易或一个坏决策。
像海龟一样思考
1、避免结果偏好
好的交易者考虑的是现在,而避免对未来考虑过多。新手则想预见到未来:如果他们赢了,他们会认为自己预测对了,感觉自己像个英雄;如果他们输了,他们会把自己当成傻瓜。这是错误的。
2、避免近期偏好
大多数交易者不仅对未来考虑过多,对过去同样考虑过多。他们会念念不忘过去所做的事,过去所犯的错误,还有过去那些失败的交易。
海龟们会从过去的经历中吸取教训,但不会为过去而烦恼。他们不会为过去所犯的错误而责备自己,也不会对过去的失败耿耿于怀。他们知道这只是游戏的一部分。
3、避免预测未来
你必须从概率的角度来考虑未来,而不是摆出一副预测的架势。文中举的例子为对唐奇安趋势系统的一次20年间月度回报率的测试结果,并作出了概率密度图与累积概率图,类似于正态分布图。此例子提到了一个参数叫R乘数,R乘数等于一笔交易的利润除以这笔交易的风险投入。风险投入的概念以一个例子说明。如果你在每盎司450美元的价位买了一份8月份黄金合约,止损退出价位是440美元,那么你的风险投入是1000美元,因为450美元与440美元之差(10美元)乘以一份合约所代表的黄金数量(100盎司),等于1000美元。如果这笔交易赚了5000美元,那么它就是一笔5R交易。以下是测试结果的概率图(自己重置的,在数据上与文章的不一样,意思是一个意思)
发现系统优势
优势这个词借鉴自赌博理论,原本是指赌场所掌握的统计学优势,它也指21点玩家可能通过记牌而获得的优势。在赌局中,如果你没有优势,从长期来看你肯定会输。
交易世界中也是如此。如果你没有优势,交易的成本会让你赔钱。交易中的优势是指一种可以利用的统计学优势,它以市场行为为基础,而这些市场行为是会重复发生的。在交易世界中,最好的优势来自于人类认知偏差所酿成的市场行为。
系统优势三要素
1、资产组合的选择:决定应该进入哪些市场的运算系统
2、入市信号:决定什么时候开始一笔交易的运算系统。
3、退出信号:决定什么时候退出一笔交易的运算系统。
如何发现优势
当某种特定的市场行为发生时,系统会发出入市信号。当你检验入市信号时,你需要关注的是伴随这种市场行为而来的价格变动。我们可以把这种价格变动分为两个部分:好的变动和坏的变动。好的变动就是对你有利的变动。换句话说,如果你买入,那么市场上涨就是好的变动。坏的变动与之相反。
交易者们把往坏方向的最大变动幅度称为MAE(maximum adverse excursion),把往好方向的最大变动幅度称为MFE(maximum favorable excursion)你可以用这些概念来直接衡量一个入市信号的优势。观察一个入市信号之后的价格变动,如果好的方向的平均最大变动幅度大于坏方向的平均最大变动幅度,说明存在正的优势。一个真正随机性的入市策略会带来大致上相等的好变动和坏变动。比如,如果你用掷硬币的方式来做出买卖决策,正面朝上就做多,背面朝上就做空,那么在你入市之后的价格变动中,MFE与MAE应该相等。
MAE=abs(买入价-计算周期内的最低价)
MFE=abs(买入价-计算周期内的最高价)
文中介绍了作者自己开发的一种优势比较工具,称之为E-比率,E-比率的计算方式如下:
1、为每一个入市信号计算指定时间段内的MFE和MAE。
2、将上述各MFE和MAE值分别除以入市时的ATR,这是为了根据波动性做出调整,将不同市场标准化。
3、将上述调整后的MFE和MAE分别求和然后除以入市信号的总次数,得到调整后的平均MFE和MAE。
4、调整后的平均MFE除以调整后的平均MAE就是E-比率。
MFE=
MAE=
n = 入市信号的总次数
E-比率 = MFE/MAE
掷硬币的E-比率在n趋近于无穷大的时候应该等于1,所以E-比率等于1时,表明没有优势,也没有劣势。当E-比率大于1时(样本量要足够),说明当前的入市信号存在优势。E-比率要结合指定的时间段计算,如果以10个交易日为周期,那么就称之为E10-比率。文中将两个入市信号结合起来使用获得了1.33的E-比率,证明通过一些策略是可以在买入时获得很大优势的。
E比率实验:
由于A股市场无法做空,所以也无需掷硬币,我的实验步骤是这样的:
1、找出A股的所有股票
2、随机抽样100只股票
3、计算每只股票从上市到当前时间的ATR值,采用海龟原版的方法计算ATR
4、针对每只股票随机选取起始时间(但是要保证往后有50个交易日)
5、计算每只股票的E-50比率
6、计算这100只股票的平均E-50比率
7、从第2步开始到第6步,重复100次
8、计算总体平均E-50比率
最后计算出来的E-50比率为1.49
同样的逻辑计算出:
E-30比率为1.35
E-10比率为1.22
以下是代码片段
"""
根据随机入市信号计算E-50比率
"""
def randomERate(self, n=50):
e_list = []
st = storage()
sql = "select symbol from stock_summary where type = 0"
stockList = self.connect.getAll(sql)
for _ in range(100):
mae_list = []
mfe_list = []
sample = getSamplePointWithRandom(100, stockList)
for ss in sample:
symbol = stockList[ss][0]
tableName = "stock_history_"+symbol
if st.isTableExist(tableName):
sql = "select `close`,`open`,`high`,`low` from "+tableName+" order by timestamp asc"
dataList = self.connect.getAll(sql)
if len(dataList) < n: #计算50日E比率,记录小于50条的过滤掉
continue
atr_list = []
dList = []
for idx in range(len(dataList)):
close_price = dataList[idx][0]
open_price = dataList[idx][1]
high_price = dataList[idx][2]
low_price = dataList[idx][3]
if idx == 0:
atr = high_price - low_price
else:
prev_close_price = dataList[idx-1][0]
tr = np.max([high_price - low_price, high_price - prev_close_price, prev_close_price - low_price])
atr = (19 * atr_list[idx - 1] + tr) / 20
atr_list.append(atr)
dList.append({&#39;high&#39;: high_price, &#39;low&#39;: low_price, &#39;close&#39;: close_price, &#39;open&#39;: open_price, &#39;atr&#39;: atr})
stop_num = len(dList) - n
random_start = random.randint(0, stop_num)
vList = dList[random_start:random_start+n]
atr_buy = vList[0][&#39;atr&#39;] #入市时的atr
v_low_list = []
v_high_list = []
for v in vList:
v_low_list.append(v[&#39;low&#39;])
v_high_list.append(v[&#39;high&#39;])
mae = vList[0][&#39;close&#39;] - np.min(v_low_list) #买入价-最低价
mfe = np.max(v_high_list) - vList[0][&#39;close&#39;] #最高价-买入价
if atr_buy == 0:
continue
mae_standard = mae/atr_buy
mfe_standard = mfe/atr_buy
mae_list.append(mae_standard)
mfe_list.append(mfe_standard)
mfe = np.sum(mfe_list)/len(mfe_list)
mae = np.sum(mae_list)/len(mae_list)
e = mfe/mae
e_list.append(e)
print(np.mean(e_list))从实验上可以看到,A股市场上的E比率通常是大于1的,由此可以看出,市场的不同会导致指标值出现较大的差异。至于这个指标对于A股市场,我认为需要辩证的看待。因为指标的算法是MFE/MAE,而MFE是买入价减周期内最高价的绝对值,MAE是买入价减周期内最低价的绝对值。而我们的市场可能会出现这么一种情况:在一个周期内出现了一段时间的暴涨和持续的低迷。如果我们的市场普遍存在这种情况,就会出现一个1.4以上的E比率。在《海龟交易法则》这本书中,这个值是一个非常有优势的入市信号,因为结合作者的交易策略(一次很大的盈利和N次较小的亏损,但是保证盈利盖过所有亏损)是非常有效的,而对于A股市场来说,在没有策略的指引下,这个指标还有意义么?这是个值得思考的问题。所以对于所谓的技术导向来投机交易,学习指标没有错,但是我的意思是不能照本宣科,一是要理解指标的深层次含义,二是要结合具体的市场,三是要结合具体的策略,否则指标对我们来说不仅没用,而且对我们有害。书中所说的也不能全信,重在理解,学习的只是思路,精髓在于利用思路去创造。
退出策略的优势
如果有可能的话,即使是系统的退出策略也应该有优势。遗憾的是,衡量退出策略的优势更不容易。这是因为退出策略与入市策略和退出信号都有关系。换句话说,你不能抛开最初建立一个头寸的缘由而去单独考察退出策略。这里面不止有一个系统要素,而是有多个不同要素,而且它们之间有着错综复杂的交互作用。
寻找交易时机
优势是在买者和卖者之间的战场上发现的。作为一个交易者,你的任务就是找到这些战场,静观谁胜谁败。
对几乎所有的交易策略来说,支撑和阻力都是一个基础概念。所谓支撑和阻力,就是指价格有一种不突破前期水平的倾向。要理解这个概念,最简单的办法就是看看价格走势图。
支撑和阻力来源于市场行为,而这些市场行为则来源于三种认知偏差:锚定效应、近期偏好和处置效应。
锚定效应是指依赖轻松可得的信息来判断价格水平的倾向。一个近期的新高或新低变成了一个新的锚,之后的每一个价位都要根据这个锚来衡量和比较。新价格是高还是低,完全取决于它是高于还是低于这个锚定价。近期的高点或低点之所以会成为锚定价,是因为它们在图表上一眼可见,在心理上对市场参与者有重大的影响。所谓的支撑位与阻力位即是锚定效应的一种表现。
大多数交易者都相信支撑和阻力现象的存在,这又进一步加强了支撑力和阻力,因为对这种现象深信不疑的交易者反过来也会促进这种现象。如果有很多交易者相信价格一旦下跌到某个水平就会有大批买入者接盘,那么他们就更容易相信价格跌到这个水平时必然会反弹。这种信念将削弱他们在这个价位卖出的意愿,因为他们更愿意晚些再卖,等价格在这个支撑位反弹后再卖。于是,对支撑和阻力现象的笃定使它成了一种自我实现的机制。
支撑位和阻力位的突破
这里就有趣了,支撑位和阻力位的突破对于两种不同策略的交易者来说,他们的交易方向是相反的。既然叫阻力位和支撑位,那么可以说价格在支撑位被支撑的概率比价格在支撑位被突破的概率要大,阻力位同理。那么如果你是一个趋势交易者,必然你在阻力位买入后的胜率就比较低,但是如果价格被突破,那么锚定效应就会失效,此时价格可能会有一个大幅度的上涨。这样在制定策略时,如果能设置一个低止损和高止盈,那么你一次的大收益往往能弥补许多次的小亏损并有剩余,从整体上来说就是盈利的。反趋势交易者就是完全相反的操作,当你在支撑位买入后,价格反弹的几率会比较高,那么你的胜率就会比较高,但是价格反弹后通常会马上到达阻力位,到达阻力位之前你就必须卖掉头寸,如果在支撑位买入后加个被突破,那么就会亏损。与趋势交易者一样,你的策略也必须设置一个低止损和高止盈,但是这个高止盈不可超过阻力价位,这样,如果你的止损和止盈是相同的百分比,那么如果在胜率上有优势,则总体上仍然是盈利的。
红色是支撑位向下突破累积概率,蓝色是压力位向上突破累积概率
我在A股市场做了一个关于支撑位与压力位的累计概率实验。实验随机选取了500个股票样本,每个股票随机选取222个连续交易日,前22个交易日当做观察周期,后200个交易日当做测试周期。先取观察周期内的最高价和最低价,当做压力位与支撑位。然后从测试周期的第一天开始测试,检查当天的收盘价是否突破压力位或支撑位,如果到测试周期的第三天,那就判断第一天、第二天、第三天是否突破了压力位或支撑位,以此类推。从图中可以看出,12天内突破的概率大概是30%左右,25天内突破的概率大概是40%左右。说明锚定价位是真实存在的,并且支撑位的锚定价要比压力位的锚定价效果更加显著。支撑位与压力位对于短线投机是非常重要的参考。
以下是代码片段:
def testSupportAndPress(self, n=1, limit=100):
st = storage()
period = self.getPeriod(n)
sql = &#34;select symbol from stock_summary where `type`=0&#34;
stockList = self.connect.getAll(sql)
sample = getSamplePointWithRandom(500, stockList) #随机抽取500个样本
support_result = {}
press_result = {}
for ss in sample:
stock = stockList[ss]
symbol = stock[0]
tableName = &#34;stock_history_&#34;+symbol
if st.isTableExist(tableName) is False: #没有股票数据跳过
continue
sql = &#34;select `low`,`high`,`close` from &#34;+tableName+&#34; order by timestamp asc&#34;
stockData = self.connect.getAll(sql)
if len(stockData) < period+limit: #股票数据不符合测试条件跳过
continue
support_result[symbol] = {}
press_result[symbol] = {}
for day in range(limit):
support_result[symbol][day + 1] = 0
press_result[symbol][day + 1] = 0
start_idx = random.randint(0, len(stockData)-period-limit)
stockInfo = stockData[start_idx:start_idx+period+limit]
periodData = stockInfo[0:period]
obData = stockInfo[period:]
period_low_list = []
period_high_list = []
for pd in periodData:
low_price = pd[0]
high_price = pd[1]
period_low_list.append(low_price)
period_high_list.append(high_price)
support_price = np.min(period_low_list)
press_price = np.max(period_high_list)
ob_close_price_list = []
for day in range(len(obData)):
close_price = obData[day][2]
ob_close_price_list.append(close_price)
for ocp in ob_close_price_list:
if ocp < support_price:
support_result[symbol][day+1] = 1
break
for ocp in ob_close_price_list:
if ocp > press_price:
press_result[symbol][day+1] = 1
break
support_fresult = {}
press_fresult = {}
x = []
support_y = []
press_y = []
for day in range(limit):
support_fresult[day+1] = {&#39;sum&#39;: 0, &#39;hit&#39;: 0}
press_fresult[day+1] = {&#39;sum&#39;: 0, &#39;hit&#39;: 0}
for day in range(limit):
x.append(day+1)
for symbol in support_result.keys():
support_fresult[day+1][&#39;sum&#39;] += 1
support_fresult[day+1][&#39;hit&#39;] += support_result[symbol][day+1]
for symbol in press_result.keys():
press_fresult[day + 1][&#39;sum&#39;] += 1
press_fresult[day + 1][&#39;hit&#39;] += press_result[symbol][day + 1]
support_y.append(round(support_fresult[day+1][&#39;hit&#39;]*100/support_fresult[day+1][&#39;sum&#39;], 2))
press_y.append(round(press_fresult[day + 1][&#39;hit&#39;]*100/press_fresult[day + 1][&#39;sum&#39;], 2))
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
font_set = FontProperties(fname=r&#34;c:\windows\fonts\simsun.ttc&#34;, size=12)
from pylab import mpl
mpl.rcParams[&#39;font.sans-serif&#39;] = [&#39;FangSong&#39;]
mpl.rcParams[&#39;axes.unicode_minus&#39;] = False
from matplotlib.font_manager import _rebuild
_rebuild()
plt.title(u&#39;A股市场支撑位与压力位突破累积概率图&#39;, fontproperties=font_set)
ln1, = plt.plot(x, support_y, color=&#39;red&#39;, linewidth=2.0, linestyle=&#39;--&#39;)
ln2, = plt.plot(x, press_y, color=&#39;blue&#39;, linewidth=3.0, linestyle=&#39;-.&#39;)
plt.legend(handles=[ln1, ln2], labels=[&#39;n days support break&#39;, &#39;n days pressure break&#39;])
ax = plt.gca()
ax.spines[&#39;right&#39;].set_color(&#39;none&#39;)
ax.spines[&#39;top&#39;].set_color(&#39;none&#39;)
plt.show()衡量风险
风险有很多种,因此衡量风险的方法也有很多种。有些大风险产生于相对罕见的事件,10年内也碰不到几次。有的风险较为常见,一年内总会出现几次。大多数交易者都担心4种主要的交易风险:
衰落:一连串损失使你的账户缩水。
低回报:回报太低,你赚到的钱微不足道。
价格动荡:一个或多个市场中出现价格的骤然变动,导致无可挽回的重大损失。
系统死亡:市场状态改变,致使曾经有效的系统突然失效。
当碰到衰落时,往往我们的大脑会产生类似下面的想法:
“万一这个系统不再管用了,怎么办?”
“万一这只是一个大滑坡的开端,怎么办?”
“万一我之前的测试方法有问题,怎么办?”
等等,这些疑虑往往会促使一个新手放弃这个系统,或者开始有选择的交易,以期“降低风险”。这经常导致交易者错失本来可以赚钱的机会。在沮丧的情绪下,在看到初始账户遭受了1/2甚至更多的损失之后,交易者终于忍无可忍的退出了。这就是新手们即使使用有效的策略也无法赚钱的原因:他们高估了自己在高风险水平下承受巨大波动的能力。
风险的量化
1、最大衰落
测试期从最高点到最低点的下跌百分比。
2、最长衰落期
测试期从一个顶峰到下一个顶峰的时间,衡量一段损失期后需要多长时间才能重新站上新的高点。
3、回报标准差
回报标准差可以以不同周期为单位计算期间内平均回报的标准差。衡量回报率的起伏状态。
4、R平方值
这个指标衡量的是实际投资回报率与平均复合增长率的吻合程度。对带息账户这一类的固定收益投资来说,R平方值为1。(其实就是实际投资回报率与平均复合增长率做线性回归后的R平方值)
回报的量化
平均复合增长率
公式:
n代表有n期
r代表每期的收益率
例如初始投资资金为100元,第一个月收益100元,总资金变为200元,第二个月亏损100元,总资金又变为100元。
那么如果按月算n = 2
r1 = (200-100)/100 = 100% = 1
r2 = (100-200)/200 = -50% = -0.5
平均复合增长率 = [(1+1)*(1-0.5)]^0.5 - 1 = 0
衡量风险与回报的综合指标
夏普比率
这里引入聚宽的夏普比率公式:
为年收益 = ,P为周期总收益,n为周期天数
为无风险收益,如银行利率,默认设置为0.04
为策略收益波动率(标准差) =
每日收益率的平均值
rp为每日收益率
例如测算一个周期5天的夏普比率,假设初始资金为100元,第一天后,总资金为120,
第二天后,总资金为110元,第三天后,总资金为130元,第四天后,总资金为140元,第五天后,总资金为135元。
利用Python算算:
def getSharp(self):
r_f = 0.04
money = [100, 110, 130, 140, 135]
r_p = (money[-1] - money[0])/money[0]
numerator = r_p - r_f
r_pi = []
for idx in range(len(money)):
if idx > 0:
rp = (money[idx] - money[idx-1])/money[idx-1]
r_pi.append(rp)
r_p_mean = np.mean(r_pi)
r_pi_diff_square = []
for rp_item in r_pi:
diff_square = np.square(rp_item - r_p_mean)
r_pi_diff_square.append(diff_square)
r_pi_diff_square_sum = np.sum(r_pi_diff_square)
baidenominator = 250 * r_pi_diff_square_sum / len(r_pi_diff_square)
sharp = round(numerator/baidenominator, 3)
print(sharp)最后计算的结果为:0.205
它代表每承受一单位总风险,会产生多少的超额报酬,by the way,上面的结果非常不错。
MAR比率
MAR比率是Managed Accounts Reports有限公司发明的一个指标,这个公司专门提供对冲基金的业绩报告。MAR比率等于年均回报率除以最大的衰落幅度,衰落是根据月末数据计算的(文中作者是通过整个周期的最高点到最低点的跌幅计算的)。这个比率是风险回报比率的一个相当快捷而又直接的衡量指标。文中作者提到,用它来剔除表现不佳的策略是非常有效的。对粗略的分析来说,它是一个绝好的工具。这句话我是这么理解的,如果制定的策略MAR比率过低,那么肯定可以淘汰。如果MAR不低,就可以进一步看别的指标了。
下面的代码是我随机抽取一个三个月的时间段,然后随机抽取的一些股票在这三个月的MAR比率,结果如下:
黑猫股份在时间段2013-12-17到2014-03-17的MAR比率是-0.0553287494628
大唐电信在时间段2013-12-17到2014-03-17的MAR比率是0.394967351489
永辉超市在时间段2013-12-17到2014-03-17的MAR比率是-0.0531024531025
麦捷科技在时间段2013-12-17到2014-03-17的MAR比率是0.00971195898055
泰山石油在时间段2013-12-17到2014-03-17的MAR比率是0.440798444327
光线传媒在时间段2013-12-17到2014-03-17的MAR比率是0.0686396712033 MAR比率代码
def MAR(self):
st = storage()
timeRange = getRandomMonthTimestampRangeForLoopback(3)
start_timestamp = timeRange[0][0]
end_timestamp = timeRange[-1][-1]
start_date = time.strftime(&#39;%Y-%m-%d&#39;, time.localtime(int(start_timestamp / 1000)))
end_date = time.strftime(&#39;%Y-%m-%d&#39;, time.localtime(int(end_timestamp / 1000)))
sql = &#34;select symbol, name from stock_summary where type = 0&#34;
rList = self.connect.getAll(sql)
id_list = getSamplePointWithRandom(10, rList)
for idx in id_list:
symbol = rList[idx][0]
name = rList[idx][1]
tableName = &#34;stock_history_&#34;+symbol
if st.isTableExist(tableName) is True:
sql = &#34;select close, high, low from &#34;+tableName+&#34; where timestamp >= {st} &#34; \
&#34;and timestamp <= {et}&#34;.format(st=start_timestamp, et=end_timestamp)
stockList = self.connect.getAll(sql)
if len(stockList) <= 0:
continue
high_list = []
low_list = []
percent = (stockList[-1][0] - stockList[0][0]) / stockList[0][0]
for item in stockList:
high_price = item[1]
low_price = item[2]
high_list.append(high_price)
low_list.append(low_price)
high = np.max(high_list)
low = np.min(low_list)
wave = (high - low) / low
MAR = percent/wave
print(&#34;{name}在时间段{st}到{et}的MAR比率是{mar}&#34;.format(name=name, st=start_date, et=end_date, mar=MAR))模仿效应与系统死亡风险
在风险回报比率上拥有傲人记录的那些策略往往都是最容易被整个行业群起模仿的策略。她们刚刚崭露头角,立即就被数十亿美元竟相追随,结果反而自毁长城,因为它们的规模已经超出了市场的承受能力。到头来,它们早早就成了系统死亡的牺牲品。
在这一点上,套利策略可能是最好的例子。最纯粹的套利实际上是一种没有风险的交易。你在一个地方买入某个东西,在另一个地方把它高价卖掉,扣掉运输或仓储成本,剩下的都是你的李瑞。大多数套利策略都不会完全没有风险,但有很多接近于没有风险。问题是,靠这样的策略赚钱是有前提条件的,那就是不同地方的同一种工具存在价格差,或者一种工具和另一种类似工具之间存在价格差。
使用某一种特定策略的人越多,价格差就消失的越快,因为这些交易者本质上都在争夺同样的机会。长此以往,这种效应会毁掉这种策略,因为它会变得越来越无利可图。
风险与资金管理
文中作者的自己管理方式为
我们把头寸分成一个个小块,也就是我们所说的头寸单位。每一个头寸单位的合约数量是根据这样的标准确定的:要让1ATR的价格变动正好等于我们账户规模的1%。对一个100万美元的账户来说,1%是10000美元。因此,我们会算出一个市场中代表着每份合约有1ATR变动幅度的美元金额,然后10000美元除以这个金额,得出每100万美元的交易资本所对应的合约数量。我们把这些数字称作头寸单位。 我认为把这段话引用到A股市场应该这么理解,假设我们的初始资金为20000元,有一只股票A,当前的价格为P,当前的ATR为N,根据策略,此时股票A发出了买入信号,那么我们应该买入多少A呢?
要让1ATR的价格变动正好等于账户规模的1%,我们的账户规模是20000,1%就是200。那么200/N就是我们应该买入的股票数,由于股票买入是以手为单位的,所以如果200/N小于100,那么我们不应该买入。设R=200/N,那么R就为1个头寸单位,不同股票因为价位和买入点的ATR均不同,所以1个头寸单位所代表的股数也不同。头寸单位同时受到买入价位和买入点的ATR影响。
通过这种方式,买入和卖出的资金管理可以做到一目了然。
海龟式交易系统
ATR通道突破系统
ATR通道突破系统是一个波幅通道系统,它把真实波动幅度均值(即ATR)用作波动性指标。350日移动平均收盘价加上7个ATR就是通道的顶部,减去3个ATR就是通道的底部。如果前一日的收盘价穿越了通道顶部,则在今日开盘时做多;如果前一日的收盘价跌破通道底部,则在开盘时做空(A股不存在做空)。当收盘价反向穿越了移动平均线,交易者们就会退出。
移动平均收盘价=N日收盘价之和/N
ATR的计算公式上面已经介绍过了,这里不介绍了。
布林格(布林线)突破系统
这个系统的布林线是通过350日移动平均收盘价加减2.5倍标准差而得出的。如果前一日的收盘价穿越了通道的顶部,则在开盘时做多;如果前一日的收盘价跌破通道的底部,则在开盘时做空。
布林线的公式:
首先中间一条线是350日移动平均线MB =
然后计算校准差std=
顶部 = MB+2.5*std
底部 = MB-2.5*std
唐奇安趋势系统
采用20日突破入市策略,10日突破退出策略。也就是价格向上突破20日均线,则买入。价格向下突破10日均线,则卖出。另外,这个系统规定了2ATR的止损退出点。2ATR的资金管理上面有介绍过,这里不再赘述。
定时退出唐奇安趋势系统
定时退出唐奇安趋势系统是唐奇安趋势系统的一个辩题,它采用的是定时退出策略,而不是突破退出策略。它在80天之后退出,没有任何形式的止损点。
三重移动均线系统
这个系统使用三种移动均线:150日、250日和350日均线。交易者在150日均线穿越250日均线时买入(向上穿)或卖出(向下穿)。最长期的350日均线扮演的是趋势过滤器的角色。只有150日和25日均线位于350日均线的同一侧时才能交易。
历史测试的谎言
历史测试结果和实际交易结果的差异主要是由四大因素造成的:
交易者效应:如果一种方法在近期赚了很多钱,那么其他交易者很可能会注意到它,开始用类似的方法模仿它,这很容易导致这种方法的效果不再想一开始那样好。
文中有一例子说明:
几年前曾有一个系统因为多年来的优异表现而变得大受欢迎,有很多经纪人开始向他们的客户提供这个系统。我曾一度听说已经有数亿美元开始追随这个系统。但就在它的影响力达到巅峰之后没多久,它的追随者们遭遇了一次旷日持久的衰落,而这样长和这样严重的衰落期在它20年的历史测试中从未出现过。这个系统有一个容易被利用的软肋。按照它的法则,如果当日的收盘价超过了某个特定水平,那么就在次日早晨一开盘时买入或卖出。由于其他交易者知道什么样的价位会引发这些买单或卖单,那么狠简单,他们完全可以赶在当日收盘之前买入,然后在次日开盘之后马上卖出。卖出价通常比买入价高得多,因为所有在一夜之间生成的买单都是在这个时候入市的,这是由系统的法则决定的。 交易者效应在任何情况下都有可能发生,不一定是某些交易者故意抢先行动的结果。只要有太多的交易者不约而同地试图利用某种市场现象,这种现象的优势就会被毁掉,至少在一段时间内不再有效,因为众多交易者的定单会削弱它的优势。
随机效应:历史测试的结果夸大了系统的内在优势也可能是纯随机性的现象。
摘抄书中原文可以了解到任何系统的短期回测结果与任何基金的近几年的成绩都不足以让我们做出正确的决策:
我曾对一个随机性入市策略进行了模拟检验,这种策略仅根据电脑模拟的掷硬币结果来决定在开盘时做多还是做空。当时我设计了一个完整的系统,采用以掷硬币结果为基础的进入策略和定时退出策略,在入市之后的若干天后退出,天数从20天到120天不等。然后我对这个系统作了100次测试。在这100次测试中,最好的一次获得了16.9%的年均回报,在10.5年的测试期内把100万美元变成了550万美元,但最差的一次测试却年均亏损20%。这说明,纯随机性事件可以导致巨大的差异。
如果加入一点优势因素,结果会怎么样呢?如果我们在这个完全随机性的系统中加入一个有正优势的趋势过滤器,那么100次测试的平均表现会显著改善。根据我的测试,平均回报率上升至32.46%,平均衰落幅度下降至43.74%。但即使加入了过滤器,各次测试结果之间仍有相当大的差异。在100次随机测试中,最好的一次达到了53.3%的年回报率和1.58的MAR比率,最大的衰落只有33.6%;但最差的一次只有17.5%的回报率,最大衰落却有62.7%之大。
当你用业绩衡量指标去区分好基金和坏基金的时候,你很容易遭遇随机效应问题。因为运气好的平庸交易者要多于运气不佳的优秀交易者。假设有1000个交易者,其中有80%接近于平均水平,只有五六个真正的高手。那么,只有五六个人有可能成为运气不佳的优秀交易者,却有800个平庸的人有机会拥有好运。如果这800个人里有2%能幸运的拥有10年的良好记录,这意味着,拥有良好记录的固然有21个人,但其中只有1/4的人是真正的优秀交易者。 最优化矛盾:选择特定参数的过程(比如选择25日移动均线而不是30日移动均线),可能降低事后测试的预测价值。
有些交易系统需要用特定的数值进行计算,选择这些数值的过程就是最优化。这些数值被称为参数。比如,长期移动均线的计算天数就是一个参数,短期均线的计算天数也是一个参数。最优化就是为这些参数选择最佳或最优化数值的过程。
所谓的最优化矛盾是有这么一个观点,在做回测时把参数调到最佳(也就是参数调到某个值的时候,回测期间受益最大,衰落较小)并不是最好的选择,因为这样可能导致过拟合,导致未来的回报与过去的回报产生相当大的差异。用文中作者的话来说,这个观点纯属放屁。作者的观点是应该将最佳参数应用到策略系统中去。作者写了比较长的篇幅来论证他的观点,我用几句话来说明他的观点:在统计学的角度来看,我们的一次回测结果其实是无数次回测结果中的一个随机值,这个随机值是无偏的。因此,我们的回测次数越多,回测结果就越接近真实的平均值。如果我们正在测试一个策略系统的某一个参数,这个参数取了不同的两个值,每个值分别测试了十次,那么肯定会有一个值的平均值产生的回报要高于另一个值产生的回报。由于每次回测结果可以看成独立同分布的,那么实际上这个回测结果是一个正态分布,那么未来利用此参数产生的回报就会落在我们回测结果平均值的某几个标准差内。为什么要选择最佳的参数,因为无论未来回报是怎么样的,高回报均值的邻域永远比低回报均值的邻域要高,所以应该选最佳参数。
过拟合:系统可能太过复杂,以至于失去了预测价值。由于它与历史数据的吻合度太高,市场行为的一个轻微变化就会造成效果的明显恶化。
过拟合比较好理解,直接摘抄原文中的一段话即可理解:
过度拟合通常发生在系统变得过于复杂的时候。有时候,你可以通过添加法则来提高一个系统的历史表现,但这仅仅是因为这些法则影响了屈指可数的几笔重要交易。添加法则会导致过度拟合,这对发生在关键时期的交易来说尤其明显。比如,假如一条法则要求你在接近最高峰的时候退出一个特别大的盈利头寸,这当然会提高你的系统表现,但如果这条法则对其它情况没有充分的适用性,这就成了过度拟合。 说简单点,过拟合就是在回测时为了得到一个好的结果而特意针对某种场景设置一个条件。而通常的策略交易,应该具有普遍适用性。
历史测试的统计学基础
测试样本的有效性
通过样本特征推断总体特征是统计学中的一个领域,也是历史检验结果的未来预测价值的理论基础。其中的核心观点是,如果你有足够大的样本,你就可以用这个样本的情况去近似推断总体情况。因此,如果你对某一种特定交易策略的历史交易记录有充分的研究,你就可以对这种系统的未来潜力得出结论。
样本分析在统计学上的有效性受两大因素的影响:一个是样本规模,一个是样本对总体的代表性。
从概念上说,许多交易者和系统测试新手指导样本规模的意思,但他们以为样本规模仅指他们所测试的交易次数。他们并不明白,假如某个法则或概念仅适用于少数几次交易,即使他们测试了上千次交易也不足以确保统计学上的有效性。
系统测试者假设过去的情况对未来的情况有代表性,如果这是事实,而且我们有足够大的样本,我们就可以从过去的情况中得出结论,并且把这些结论应用于未来的交易。但如果我们的样本对未来不具代表性,那我们的测试就毫无用处。比如我们的市场通常有4种状态,但我们的测试样本只包含其中的某一种或两种,那么我们的样本就不具有代表性。所以,你的测试方法必须尽可能的提高你所测试的样本对未来的代表性。
找出稳健的衡量指标
在系统测试中,你要做的是观测相对表现,分析未来潜力,判定一个特定理念是否有价值。但这里面有个问题,那就是公认的那些业绩衡量指标并不是非常稳定,也就是说,它们不够稳健。这就使评判一个理念的相对优势变得非常困难,因为寥寥几次交易中的微小变化就能对这些不稳健指标的值产生巨大的影响。
如果对数据稍作改变并不会显著影响一个统计指标,我们就说这个指标是稳健的。MAR比率、平均复合增长率(CAGR)和夏普比率用作相对表现的衡量指标。但这些指标并不稳健,因为它们对测试期的起始日和终止日非常敏感。以平均复合增长率为例,可以以时间为横坐标,平均复合增长率的对数为纵坐标(这样可以让平均复合增长率关于时间的函数看起来像一个线性模型)做散点图,你会发现当截取不同时间段的时候,时间起止日的不同会使它们的连接线的斜率产生很大的变化,这说明一点点时间上的改变就对复合增长率有显著的影响。如果我们将这些散点拟合成一条直线(使用一元线性回归模型),取复合增长率时,我们取不同时间点上的拟合值,那么这个拟合的复合增长率会稳定很多。文中作者对这个拟合的复合增长率取了一个新名字,叫回归年度回报率,简称RAR。(regressed annual return)
平均复合增长率
上图红线代表实际的平均复合增长率,蓝线是通过最小二乘法拟合的平均复合增长率,也就是RAR,可以看到,对于实际的平均复合增长率,如果拉长或缩短横坐标的范围,Y值两点连线的斜率会有较大的变化,这代表指标是不稳健的,如果将它拟合成一条直线,斜率不变,所以更加稳定。指标值对于实际值的残差较小,表明拟合值不会有太大的误差。 RAR代码如下:
def CAGRlog(rate):
r = []
for ra in rate:
r.append(np.log(1+ra))
return np.sum(r)
def testCAGRLog(self):
p_list = np.random.rand(10)
X = []
Y = []
for idx in range(1, len(p_list)+1):
X.append(idx)
pl = p_list[:idx]
result = CAGRlog(pl)
Y.append(result)
import statsmodels.api as sm
X_fit = sm.add_constant(X)
model = sm.OLS(Y, X_fit)
results = model.fit()
Y_fit = results.predict()
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
font_set = FontProperties(fname=r&#34;c:\windows\fonts\simsun.ttc&#34;, size=12)
from pylab import mpl
mpl.rcParams[&#39;font.sans-serif&#39;] = [&#39;FangSong&#39;]
mpl.rcParams[&#39;axes.unicode_minus&#39;] = False
from matplotlib.font_manager import _rebuild
_rebuild()
plt.title(u&#39;平均复合增长率&#39;, fontproperties=font_set)
ln1, = plt.plot(X, Y, color=&#39;red&#39;, linewidth=2.0, linestyle=&#39;--&#39;)
ln2, = plt.plot(X, Y_fit, color=&#39;blue&#39;, linewidth=2.0, linestyle=&#39;--&#39;)
plt.legend(handles=[ln1, ln2], labels=[&#39;actual value&#39;, &#39;fit value&#39;])
ax = plt.gca()
ax.spines[&#39;right&#39;].set_color(&#39;none&#39;)
ax.spines[&#39;top&#39;].set_color(&#39;none&#39;)
plt.show()如果将夏普比率的分子替换成RAR,那么夏普比率同样变成了稳定性指标。
替换MAR比率的稳健风险回报比率,作者给它起名叫R立方(robust rist/reward ratiio)。R立方的分子就是RAR,分母是个新指标,作者称之为长度调整平均最大衰落。这个分母指标有两个要素:平均最大衰落和长度调整。
平均最大衰落就是5次最大衰落幅度的平均值。长度调整就是将这5次衰落期的平均天数除以365天,然后用这个结果乘以平均最大衰落。平均衰落天数的计算原理与平均衰落幅度相同,也就是将5次衰落期的天数相加再除以5。
设5次最大的衰落幅度分别为a, b, c, d, e,那么平均最大衰落 = (a+b+c+d+e)/5
设5次最大的衰落幅度的天数分别为u, v, w, x, y,那么长度调整 = (u+v+w+x+y)/(5*365)
稳健风险回报比率的分母 =[ (a+b+c+d+e)/5] * [(u+v+w+x+y)/(5*365)]
从虚拟测试到实战交易
怎么判断你在实际交易中可能获得什么样的成果?对历史测试来说,这或许是最有趣的问题之一。
要想得到有意义的答案,你必须理解影响系统表现的因素,使用稳健指标的必要性,以及采集足够大的代表性样本的重要性。一旦你做到了这一点,你就可以开始思考市场变换的潜在影响,思考为什么连老练的交易者设计的优秀系统也会经历业绩的盛衰起伏。你不可能知道,也不可能预见到一个系统的表现会怎么样,这是现实。充其量,你只能借用有效的工具来判断系统的潜在效果,以及影响这种效果的因素。
如果一个系统在最近一段时间表现得特别出众,这有可能是个运气问题,或许市场对这种系统来说正处于理想的状态中。一般来说,这种冒尖的系统在好时期过后很容易转入困难时期,不能指望它在未来会重现这种好运的表现。这也许会发生,但你不能寄希望于运气。你更有可能经历业绩的下滑。
参数检验调整
1、在决定采用一个系统之前先体验一下参数的作用是个好习惯。挑出几个系统参数,大幅调整参数值,然后看看效果怎么样?如果效果显著,说明系统参数对于系统的影响是显著的。
2、随便选择8-10年前的一天,用这一天之前的所有的数据进行最优化,当你得出了最优化参数值后,再用这一天之后两年内的数据检验一下这些参数值。这就跟机器学习中要建立训练集和测试集一样的道理。
蒙特卡洛检验
摘抄自wiki的对蒙特卡洛检验的一个简单说明:
假设我们要计算一个不规则图形的面积,那么图形的不规则程度和分析性计算(比如,积分)的复杂程度是成正比的。蒙特卡罗方法基于这样的想法:假设你有一袋豆子,把豆子均匀地朝这个图形上撒,然后数这个图形之中有多少颗豆子,这个豆子的数目就是图形的面积。当你的豆子越小,撒的越多的时候,结果就越精确。借助计算机程序可以生成大量均匀分布坐标点,然后统计出图形内的点数,通过它们占总点数的比例和坐标点生成范围的面积就可以求出图形面积。 文中所用的利用蒙特卡洛检验思想的一种方法是拼接净值曲线:在初始净值曲线中随机选择一些小段,然后将它们打乱次序组成一个新的净值曲线。按如此方式生成几千个新的曲线,然后计算曲线对应的RAR,通过RAR的直方图和累积概率图来找出90%置信区间下的RAR的水平。
以上详细阐述了《海龟交易法则》这本书的所有观点、思路及具体操作,下面给出原版海龟交易系统。以下均摘抄原文:
入市策略
交易者大多从入市信号的角度来评判一个特定的交易系统。他们相信,入市策略是一个交易系统最重要的一个环节。
所以他们可能想不到,海龟们使用的是一个以理查德-唐奇安的通道突破系统为基础的非常简单的入市系统。
海龟们使用两个有所差异但也彼此相关的入市系统,我们称为系统1和系统2。我们可以自由决定如何在这两个系统之间分配资金。有的海龟只用系统2,有的在两个系统上各投入50%的资金,还有的采用其他组合。这两个系统分别是:
系统1:以20日突破为基础的短期系统
系统2:以55日突破为基础的长期系统
突破
突破是指价格超越了过去一定时期内的最高点或最低点。所以,20日突破就是指价格超越了过去20天的最高或最低点。
海龟们总是在突破发生时立即入市交易,不会等到当日收盘或次日开盘时。在跳空开盘的情况下,假如开盘价已经跳过了突破价,海龟们就在开盘时入市。
系统1入市法则
只要价格超越20日最高或最低点一个最小单位,海龟们就马上行动。如果价格超越了20日高点,海龟们就买入1个头寸单位,开始做多。如果价格跌破了20日低点,海龟们就卖出1个头寸单位,开始做空。
但是,假如上一次突破是一次盈利性突破(也就是可以带来一次盈利的交易),那么系统1的当前入市信号将被忽略。注意:对这一法则来说,上一次突破就是指市场的上一次实际突破,不管交易者当时采取了突破交易还是根据这一法则而忽略了那次突破。那么什么是亏损型的突破呢?如果突破日之后的价格在头寸有机会退出获利(根据10日突破退出法则)之前发生了2N幅度的不利变动,这就被视为一次亏损性的突破。
对这一法则来说,上一次突破的方向并不重要。因此,无论上一次突破是向上突破还是向下突破,只要是亏损型突破,那么目前的新突破(无论是向上还是向下突破)就会被视为有效的入市信号。
不过,如果一次突破因为这条法则而被忽略,那么交易者将在55日突破点入市,这是为了避免错过重大趋势。这个55日突破点被视为一个保障性突破点。
在任何时候,如果一个交易者处于离场等待的状态,那么总有某个价位能引发空头入市信号,也总有某个更高的价位能引发多头入市信号。如果上一次突破是亏损的,那么新突破(也就是20日突破点)将更接近于当前价格;如果上一次突破是盈利性的,那么新突破点可能离当前价远的多,因为那有可能是个55日突破点。
系统2入市法则
只要价格超越55日最高点或最低点一个最小单位,我们就入市。如果价格超越了55日高点,海龟们就买入1个头寸单位,开始做多。如果价格跌破了55日低点,海龟们就卖出1个头寸单位,开始做空。
对系统2来说,所有突破都被视为有效信号,无论上一次突破是亏损性还是盈利性的。
逐步建仓
海龟们首先在突破点建立1个单位的头寸,然后按1/2N的价格间隔一步一步扩大头寸。这个1/2N的间隔以上一份定单的实际成交价格为基础。所以,如果最初的突破交易发生了1/2N的成交价偏差,那么新定单的价格将与突破点相差1N,也就是最初1/2N的偏差加上1/2N的标准间隔。
这个过程将继续下去,一直到头寸规模达到上限。如果市场足够活跃,我们有可能在一天内加满4个头寸单位。
请看下面的例子:
黄金
N=2.5
55日突破价=310.00
第一个单位:310.00
第二个单位:310.00+1/2*2.5=311.25
第三个单位:311.25+1/2*2.5=312.50
第四个单位:312.50+1/2*2.5=313.75
止损标准
海龟们根据头寸风险来设定止损标准。任何一笔交易的风险程度都不得超过2%。
由于1N的价格变动代表着账户净值的1%,在2%的风险限制下,价格变动的上限就是2N。海龟们止损标准就是2N:对多头头寸来说,止损价比入市价低2N;对空头头寸来说,止损价比入市价高2N。
为了把整体头寸风险控制在最低水平,如果我们(按1/2N的价格间隔)后续补充了头寸单位,那么之前头寸单位的止损点将相应的调整1/2N。一般来说,这意味着整个头寸的止损点将与最新添加的头寸单位相距2N的距离。不过,如果头寸补充的价格间隔因为市场变化过快或开盘跳空等情况而大于1/2N,止损标准也会有所变化。
下面是一个例子:
原油:
N=1.2
55日突破价=28.30
入市价/止损价
第一个单位 28.30/25.90 diff=2.4=2N
---------------------------------------------
第一个单位 28.30/26.50 diff=1.8=1.5N
第二个单位 28.90/26.50 diff=2.4=2N
第一个头寸单位和第二个头寸单位买入价相差1/2N=0.6
---------------------------------------------
第一个单位 28.30/27.10 diff=1.2=1N
第二个单位 28.90/27.10 diff=1.8=1.5N
第三个单位 29.50/27.10 diff=2.4=2N
---------------------------------------------
第一个单位 28.30/27.70 diff=0.6=1/2N
第二个单位 28.90/27.70 diff=1.2=1N
第三个单位 29.50/27.70 diff=1.8=1.5N
第四个单位 30.10/27.70 diff=2.4=2N
假如第四个单位因为市场跳空高开在每股30.80美元成交,那么结果将变为:
第一个单位 28.30/27.70 diff=0.6=1/2N
第二个单位 28.90/27.70 diff=1.2=1N
第三个单位 29.50/27.70 diff=1.8=1.5N
第四个单位 30.80/28.40 diff=2.4=2N
备选止损策略:双重损失
海龟们也学习了另外一种叫做双重损失的止损策略。
在双重损失策略下,每一笔交易的风险上限不是2%,而是0.5%。也就是说,价格波动的上限是1/2N。在一个头寸单位止损退出后,交易者将在价格恢复到最初的入市价时重新建立这个单位。
比如,如果采用双重损失止损策略,上述原油交易将变成下面的样子:
原油:
N=1.2
55日突破价=28.30
入市价/止损价
第一个单位 28.30/27.70 diff=0.6=1/2N
---------------------------------------------
第一个单位 28.30/27.70 diff=0.6=1/2N
第二个单位 28.90/28.30 diff=0.6=1/2N
---------------------------------------------
第一个单位 28.30/27.70 diff=0.6=1/2N
第二个单位 28.90/28.30 diff=0.6=1/2N
第三个单位 29.50/28.90 diff=0.6=1/2N
---------------------------------------------
第一个单位 28.30/27.70 diff=0.6=1/2N
第二个单位 28.90/28.30 diff=0.6=1/2N
第三个单位 29.50/28.90 diff=0.6=1/2N
第四个单位 30.10/29.50 diff=0.6=1/2N
退出(止盈和止损双重标准)
系统1采用10日突破退出法则:对多头头寸来说,在价格跌破过去10日最低点时退出;对空头头寸来说,在价格超过10日最高点时退出。总之,如果价格发生了不利于头寸的10日突破,所有头寸单位都要退出。
系统2则采用20日突破退出法则:对多头来说2是日0向下突破,对空头来说是20日向上突破。只要价格发生了不利于头寸的20日突破,所有头寸单位都会退出。 以下是我用系统入市法则2,在A股市场上做的回测。由于原版的海龟交易法则是基于期货市场执行的,与A股市场有较大的差异,所以我也只能尽量贴合原版的规则,而无法做到一模一样。以下是我的改编版规则:
假设初始资金为1000000(100万)
1、找出近55日的最高价。
2、计算当日的ATR值。
3、如果当日收盘价大于55日的最高价一个ATR,就产生一个入市信号。
4、如果a股产生了一个入市信号,那么就买入100万 * 0.01 / (ATR * 100) 手(向下取整)该股股票。
5、买入的股票不重复,比如头一天买入了a股,第二天a股继续突破了55日最高价,那么将不买入此股票。
6、如果找到了多个产生入市信号的股票,则按顺序买入,直到资金不足为止。
7、退出机制:设自买入之日算起到当前日前一天里的最高收盘价为H,那么如果当前收盘价-H <= -1 * ATR,就卖出此股票。
奉上聚宽代码
# 导入函数库
from jqdata import *
import numpy as np
import math
import copy
# 初始化函数,设定基准等等
def initialize(context):
# 设定沪深300作为基准
set_benchmark(&#39;000300.XSHG&#39;)
# 开启动态复权模式(真实价格)
set_option(&#39;use_real_price&#39;, True)
# 输出内容到日志 log.info()
log.info(&#39;初始函数开始运行且全局只运行一次&#39;)
# 过滤掉order系列API产生的比error级别低的log
# log.set_level(&#39;order&#39;, &#39;error&#39;)
g.all_stock = get_all_securities(types=[&#39;stock&#39;], date=None).index.values
g.result = []
### 股票相关设定 ###
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type=&#39;stock&#39;)
## 运行函数(reference_security为运行时间的参考标的;传入的标的只做种类区分,因此传入&#39;000300.XSHG&#39;或&#39;510300.XSHG&#39;是一样的)
# 收盘时运行
run_daily(market_close, &#39;14:50&#39;, reference_security=&#39;000300.XSHG&#39;)
def sellSignal(context):
for stock in context.portfolio.positions.keys():
hit_record = None
for item in g.result:
if item[&#39;stock&#39;] == stock:
hit_record = item
break
if hit_record is None:
break
stock_price_info = get_price(stock, start_date=hit_record[&#39;t&#39;], end_date=context.current_dt, frequency=&#39;daily&#39;, fields=[&#39;open&#39;, &#39;close&#39;, &#39;high&#39;, &#39;low&#39;], skip_paused=True, fq=&#39;pre&#39;, fill_paused=False)
if stock_price_info.empty is True:
continue
stock_price_info = stock_price_info.iloc[:stock_price_info.shape[0]-1, ]
if stock_price_info.empty is True:
continue
stock_price_info = stock_price_info.dropna(axis=0, how=&#39;any&#39;)
if stock_price_info.empty is True:
continue
close_list = stock_price_info[&#39;close&#39;].tolist()
break_price = np.max(close_list)
cur_price = context.portfolio.positions[stock].price
#当前价减去买入后的最高价小于或等于1个入市时的ATR时退出
if cur_price - break_price <= hit_record[&#39;atr&#39;] * -1:
closeable_amount = context.portfolio.positions[stock].closeable_amount
order_res = order(stock, closeable_amount * -1)
if order_res is not None:
if order_res.status == OrderStatus.held:
g.result.remove(hit_record)
def buySignal(context):
for stock in g.all_stock:
cpp_value_list = []
for cpp in context.portfolio.positions.keys():
cpp_value_list.append(context.portfolio.positions[cpp].value)
if len(cpp_value_list) > 0:
if context.portfolio.available_cash < np.min(cpp_value_list):
break
position_stock_list = context.portfolio.positions.keys()
#如果这只股票已经持仓了,则跳过
if stock in position_stock_list:
continue
stock_price_info = get_price(stock, count=100, end_date=context.current_dt, frequency=&#39;daily&#39;, fields=[&#39;open&#39;, &#39;close&#39;, &#39;high&#39;, &#39;low&#39;], skip_paused=True, fq=&#39;pre&#39;, fill_paused=False)
sumRow = stock_price_info.shape[0]
last_stock_price_info = get_price(stock, count=1, end_date=context.current_dt, frequency=&#39;1m&#39;, fields=[&#39;open&#39;, &#39;close&#39;, &#39;high&#39;, &#39;low&#39;], skip_paused=True, fq=&#39;pre&#39;, fill_paused=False)
last_stock_close_price = last_stock_price_info.iloc[0,:][&#39;close&#39;]
last_stock_high_price = last_stock_price_info.iloc[0,:][&#39;high&#39;]
last_stock_low_price = last_stock_price_info.iloc[0,:][&#39;low&#39;]
last_stock_pdc = stock_price_info.iloc[stock_price_info.shape[0]-2, :][&#39;close&#39;]
if math.isnan(last_stock_close_price) or math.isnan(last_stock_high_price) or math.isnan(last_stock_pdc):
continue
if sumRow - (55 + 1) >= 0:
start_idx = sumRow - (55 + 1)
#获取当前交易日的前一天到前55天的数据
period_data = stock_price_info.iloc[start_idx:sumRow-1, :]
#过滤掉停牌的日子
period_data = period_data.dropna(axis=0, how=&#39;any&#39;)
#前55天再往前推20个交易日的数据,用来计算前55天里,第一天的ATR
if start_idx - 20 >= 0:
pre_atr_stock_df = stock_price_info.iloc[start_idx-20:start_idx, :]
else:
pre_atr_stock_df = stock_price_info.iloc[:start_idx, :]
#过滤掉停牌的日子
pre_atr_stock_df = pre_atr_stock_df.dropna(axis=0, how=&#39;any&#39;)
pre_atr_list = []
for pas_row in range(pre_atr_stock_df.shape[0]):
high_price = pre_atr_stock_df.iloc[pas_row, :][&#39;high&#39;]
low_price = pre_atr_stock_df.iloc[pas_row, :][&#39;low&#39;]
if pas_row == 0:
atr = high_price - low_price
else:
pdc = pre_atr_stock_df.iloc[pas_row-1, :][&#39;close&#39;]
compare_list = [high_price-low_price, abs(high_price-pdc), abs(pdc-low_price)]
atr = np.max(compare_list)
pre_atr_list.append(atr)
#第一天的ATR
start_atr = np.mean(pre_atr_list)
else:
#如果没有前55天的数据,那么就直接选第一天数据到倒数第二条数据,并且用第一天的最高价-最低价当做ATR
period_data = stock_price_info.iloc[:sumRow-1, :]
period_data = period_data.dropna(axis=0, how=&#39;any&#39;)
start_atr = period_data.iloc[0, :][&#39;high&#39;] - period_data.iloc[0, :][&#39;low&#39;]
atr_list = [start_atr]
#算前55天内,每天的ATR
for prow in range(1, period_data.shape[0]):
high_price = period_data.iloc[prow, :][&#39;high&#39;]
low_price = period_data.iloc[prow, :][&#39;low&#39;]
pdc = period_data.iloc[prow-1, :][&#39;close&#39;]
tr = np.max([high_price-low_price, abs(high_price-pdc), abs(low_price-pdc)])
cur_atr = (19 * atr_list[prow-1] + tr) / 20
atr_list.append(cur_atr)
#入市时的atr,也就是最后一天的ATR
last_atr = (19 * cur_atr + np.max([last_stock_high_price-last_stock_low_price, abs(last_stock_high_price-last_stock_pdc), abs(last_stock_low_price-last_stock_pdc)])) / 20
#55天内的最高价
high_list = period_data[&#39;high&#39;].tolist()
if len(high_list) > 0:
break_price = np.max(high_list)
#如果当前价-55日最高价大于等于1个ATR,则入场
if last_stock_close_price - break_price >= last_atr:
#计算头寸单位
hand = int(int(context.portfolio.starting_cash) * 0.01/(last_atr * 100))
order_res = order(stock, hand*100)
if order_res is not None:
if order_res.status == OrderStatus.held:
#买入成功后,记录入市时的ATR等信息
g.result.append({&#39;stock&#39;: stock, &#39;t&#39;: copy.deepcopy(context.current_dt), &#39;order_id&#39;: order_res.order_id, &#39;atr&#39;: last_atr})
def market_close(context):
buySignal(context)
sellSignal(context)
由于时间有限,以下是随机时间段做了两次回测的结果,时间周期为三个月
2011年3月1日到2011年6月1日
2014年1月15日到2014年4月15日
从结果来看,效果并不理想,说明市场不同,法则也不同,切莫死记硬背,拿来就用。但是海龟的交易思想是值得借鉴的。总结一下海龟交易的思想:
1、制定交易策略,制定交易策略主要分几个方面:一、发现市场优势,也就是我们得平衡胜率和单次收益,如果胜率高,对于单次收益的要求就可以适当降低,如果胜率低,则相反,可以通过计算寻找一个好的平衡点。二、使用风险衡量指标,对我们的策略进行风险衡量。三、资金管理,可以使用海龟的ATR法,也可以使用等量资金。资金管理的方式很多,主要的目的就是控制风险,我们能清楚的知道每次亏损的最大额度,我们能清楚的知道我们能承受最大的连续亏损是多少次,我们能清楚的知道我们破产的概率是多大。
2、有效回测,要把回测的时间定在一个较长的范围内,这样既可以覆盖市场所有的状态,也可以避免前文所说的譬如近期偏好的一些错误。然后要进行随机性的多次测试,用测试的术语来说就是,我们要把用例覆盖全,尽量做全量测试,尽量把逻辑的每个分支都覆盖到。用统计学术语来说就是,如果我们的样本足够多,我们离真相就越近。这样我们计算出来的风险指标就更有说服力,我们进行交易时的信心就会更足。
3、也就是心理,在我看来,你说你是价值投资也好,量化交易也罢,投机也行,反正都是炒股,只是流派风格不同,就像有的人使刀,有的人使剑,本身不存在对错,在各自的领域都有佼佼者,也有垃圾,说到底,价值投资、量化交易只是拿来赚钱的工具。差劲的剑客,活不过几天;合格的剑客,能立足于江湖;卓越的剑客,能笑傲江湖。如果你有一本剑谱,练熟它,相信它,执行它,那么起码能活着,可能还活的不错。如果你能写一本剑谱,那就能在这个江湖呼风唤雨,要尊严有尊严,要女人有女人。我本人也还处于找剑谱的路上,但我相信是这么个道理,以上。
以上内容如有误欢迎各位指出,本人也是凭自己有限的能力想尽量解释清楚海龟交易法则的详细思想和操作方法。 |
|