读《趋势永存:打败市场的动量策略》

前言

最近写了不少以动量因子为核心的量化策略,结果收益和回撤都不太理想

跑去社区发了个帖子贴了下自己的回测情况,有人推荐我看看这本书,遂有如下经历

原书思想

简单点说,整个策略是期望基于动量因子寻找到有上升趋势的股票,买入并持有直到趋势消失,从而在牛市中获取超额利润

作者以标普500作为股票池,他的理由是:

一只股票能加入到指数的部分原因是其过去价格走势强劲。当股票从指数中退出时,通常是价格表现不佳,跌到所要求市值之下。这使得标普500指数和其他大多指数一样在一定程度上都是动量策略。

关于动量效应的解释:

当一只股票的股价上涨一段时间后,继续上涨的可能性要高于回落的可能性,比其他股票上涨的快的股票会继续比其他股票上涨的快

而且作者不是仅仅是通过计算股票的年化收益率作为动量因子来筛选股票(后面我会列举具体的计算步骤),他还有一些附加条件,比如:

  1. 股票价格必须高于其100日均线。如果不是,说明它并不符合动量的标准。因为在上涨的市场中,排名靠前的股票价格都远远高于其100日均线,但是如果在熊市或者牛熊市转换之际,上涨的股票很少,这条规则可以确保你不会买入那些横盘或者下跌的股票。

  2. 注意价格缺口。如果某只股票在过去90天里有超过15%的价格缺口(股价大幅变动并伴随极少的交易量),那么它也会被取消买入资格,因为如果你不排除这一情况,就有可能买入并非真正动量股的股票。比如说短期冲击可能导致股价大幅波动,有时即使我们对年化收益率做了一定的修正,仍无法抵消这一影响,这就与我们希望买入稳步上升的股票的初衷相违背了。

归根结底,对动量因子的应用,其实就是一种趋势跟随的实操方案,而趋势跟随的弊端及补偿方案,原书中也提到过:

当市场横盘整理或是快速切换方向的时候,趋势跟随者便会亏损。对于个别市场或行业,这一现象可能会持续多年。在极端情况下,甚至会持续十年。趋势跟随的核心前提是基于多元化。通过同时交易多种不同类别的资产,其获得成功的概率是非常高的,以至于有足够的资金来弥补在某些资产类别上的损失

这一点在实际回测中也被很好的印证了,基本跑不过大盘

image

上图15年熊市末期到18年末的收益情况,红线是沪深300收益,蓝线是策略收益

当然,除了依据动量因子给池中的股票做排名之外,作者也提到了头寸规模

他说的这句话一定要画上重点:我们不是分配资金,而是分配风险

很多人给资产分配不同的权重时往往容易忽视背后的逻辑,要记住,我们正在做的是均衡风险,而不是表面上的资金分配

最后,关于卖出时机,这一点不是动量策略要考虑的,因此书中并未提及止损方法

回测的具体步骤

首先贴下代码逻辑,这是我最近的心得之一,回测前一定要画好流程图,后面撸代码时效率会高很多

image

上面是根据原书的策略优化后作出的第一版流程图,实际回测时我是有部分改动的,后续会提到

下面讲讲核心算法

股票排名

指导思想:找到一类稳步上升的股票,不仅随着时间的推移获得可观的收益,而且还尽可能平稳地移动

这里主要以两个指标为依据:

  1. 股票年化收益 2. 股票的波动率

关于第一点,这里所说的年化收益其实是通过指数回归计算日涨幅从而得到的,目的是为了量化动量这一指标,收益越高,动量越大

股票的波动率是借助r-squared这个判定系数来衡量价格序列与回归直线的拟合程度,拟合性越差,判定系数越低,给最终分数添加更高的惩罚

所以最后将二者乘积作为股票的分值

具体计算方法:

  1. 对价格序列取自然对数

  2. 对处理后的价格序列计算线性回归方程

  3. 将方程的斜率作为日收益,再计算其250次方获得年化收益

  4. 通过RSQ()函数计算判定系数(r-squared)

以上是原书的计算方法,详情可参考我的代码:

def get_socre(stock):
  ''' 基于股票年化收益和判定系数打分

  Returns:
      score (float): score of stock
  '''
  data = attribute_history(stock, g.stock_mean_day, '1d', ['close'])
  y = data['log'] = np.log(data.close)
  x = data['num'] = np.arange(data.log.size)
  slope, intercept = np.polyfit(x, y, 1)
  annualized_returns = math.pow(math.exp(slope), 250) - 1
  r_squared = 1 - (sum((y - (slope * x + intercept))**2) / ((len(y) - 1) * np.var(y, ddof=1)))
  return annualized_returns * r_squared

头寸规模

关于波动的计算,作者引入ATR,即Average True Range(平均真实波幅)这一指标,来衡量股票价格波动

其中,TR =∣今日最高价 - 今日最低价∣和∣今日最高价 -昨日收盘价∣和∣今日最低价 - 昨日收盘价∣的最大值

原书ATR取近20日内TR的均值

关于风险因子,用于设定头寸规模,举个例子:

总资金10万,风险因子10个基点(0.001),目标股价118.93,ATR为3.26(股价日振幅)

则,对应股票应持股数量 = 10万 X 0.001 / ATR, 股票权重 = 持股数量 X 股价(118.93)

最后,设定一个阈值,一旦目标股票当前的资金暴露风险与期望风险相差大于阈值,则触发再平衡操作,这么做的一个考量是为了减少换手率,防止过多的小额交易

参考代码:

def get_expected_position(stock, context):
    ''' 根据ATR和风险因子计算股票的期望仓位

    Returns:
        float: 目标股票的期望投入金额
    '''
    data = attribute_history(stock, g.ATR_day+1, '1d',
                             ['high', 'low', 'close'])
    ATR = talib.ATR(data.high, data.low, data.close, timeperiod=g.ATR_day)[-1]
    stock_price = data.close[-1]
    expected_position = context.portfolio.total_value * g.risk_factor * stock_price / ATR
    return expected_position


def get_diff_position(stock, context):
    ''' 股票的当前仓位与期望仓位的差值百分比

    Returns:
        float
    '''
    expected_position = get_expected_position(stock, context)
    now_position = context.portfolio.positions[stock].value
    return abs(now_position / expected_position - 1)

最后

策略表现:

image

本次回测只是复制原书的整个策略,作者选用的标普500,我选取的沪深300,并未依据A股市场做任何变动

但是从牛市情况来看,原策略在国内的表现也还是可圈可点的,至于熊市和震荡市的表现,就得结合其他策略做进一步的改进了

比如原策略对熊市的判断是依据标普500的200日均线,这一点用在A股的结果从上图就能看出

关于参数的优化,作者说的一些话我很中意,比如:

如果你运行一个优化算法,你可能得出这样的结论:一个237天或178天的移动平均是最有效的。那会让你以为这跟未来一定是有关系的。其实你得到的就是一个在特定历史时期下的曲线拟合。你真正要做的是仔细思考一些理念,而不是某个精确的数字。

做量化交易很容易陷入过度优化的误区,我的观念是定性首先比定量要重要,一个策略如果要求一套非常精确的规则和参数,那这样的策略通常不会是健壮的策略。

从这一点看,上面的动量策略的表现其实我是比较满意的

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容