追随美的指引-纪念西蒙斯

周六早上醒来,James Simons(西蒙斯)辞世的消息刷屏了。多数人知道他,是因为他的财富和量化对冲基金公司-文艺复兴。但他更值得为人纪念的身份,则是数学家和慈善家。

西蒙斯1938年生于麻省,毕业于MIT,在UC Berkeley获得博士学位。曾任职于MIT, Harvard,后来在SBU(Stony Brook University,纽约州立大学石溪分校)担任了数学系主任。此后他跨行到了金融界,开创了传奇式的文艺复兴对冲基金。

西蒙斯作为数学家,最大的成就是他与陈省身共同提出的Chern-Simons Theory,这一理论在低维拓扑学、量子场理论和弦理论中有着广泛的应用。


下图就是据说是流体冲击情景下的Chern-Simons Form随曲面三维位置和极坐标位置变化的示意图(根据鹿鹿数模@知乎提供的代码绘制),因为觉得很美,根据西蒙斯的“指导原则”:“be guided by beauty”,就把这个图贴上来。


Tip

Be guided by beauty. 2014年在MIT举办的AMS(美国数学学会)Einstein Lecture上,西蒙斯应邀发表了名为《Mathematics, Common Sense, and Good Luck: My Life and Careers》的演讲,在演讲中,他给听众介绍了这样一些指导原则:

始终去做一些新的东西;
追随美的指引;
不要轻易放弃。

西蒙斯很早就有做生意的兴趣。在26岁时,因为背上债务,不得不委身于军方,帮助他们破译密码。这项工作对他来说十分轻松,但报酬却很高。不过,由于他毫不避讳地坚持反战,这与他上司的上司的立场截然相反,最终被解雇。

随后他进入石溪分校,担任了数学系主任。这项工作中,最重要的任务可能就是挖角。可能是因为之前一直有做生意的历练,他在这一任务中如鱼得水,囊括了大量人才,一度导致同州的康纳尔大学向州长抗议。最终,经他的努力,石溪分校数学系终于从一文不名发展到有20多位数学家、成为世界顶尖的几何学研究中心之一。这也为他后来成功创办文艺复兴打下了人才基础。

不过,伯乐也有相不中马的时候。他在石溪最大的损失,就是没能发掘出当时还在担任助教的丘成桐,也就是最近因为在华中科技大学的一个演讲,受到项立刚等等“专家”攻击的数学巨搫。不过,西蒙斯一直跟华人科学家很有缘分。下图是他与杨振宁、陈省身的合影:


75%

虽然在平凡之辈看来,西蒙斯是天纵英才,但也有他解决不了的难题。在试图证明某些有几何定义的数字(比如\(pi\),几乎在任何情况下都是无理数这一猜想时,遇到了挫折,让他变得灰心丧气甚至有点绝望,并最终将他推向了金融界。

1978年,西蒙斯成立投资公司并专职做交易。

对于他的离职,不同的人有不同的看法。他的父亲说他放弃终身教职是犯了一个错误,他更愿意别人问起儿子的工作时,能够回答说是“数学家”而不是“企业家”。大部分数学家认为以追求金钱为目的是不光彩的。他的同事沙利文认为,数学是神圣的,吉姆是认真的数学家,是可以解决最棘手问题的人。而康纳尔大学的卡莫纳则批评得更为尖锐,认为他是“把灵魂卖给了魔鬼”。


有这样一群“清高”的人作为土壤,这也许是他们基础学科得以发展的重要原因之一吧。

不过,对西蒙斯来说,亦师亦友的陈省身对他离职的看法,则更耐人寻味。陈省身说,你又不是希尔伯特式的人物,转就转呗,反正数学界也不会损失什么。

西蒙斯转型做投资也并非一翻风顺。直到1989年,他们的交易策略仍然是主观和量化相结合的。他回忆到,有时候你像个英雄一样赚钱,有时候又像个狗熊一样的赔了。后来,他们决定完全转向量化,并且放弃一切宏观指标。由此开始,文艺复兴,特别是旗下的大奖章基金,开始了自己的狂飙时代。

从1988年到2019年,西蒙斯的大奖章基金年化收益率为39%(费前收益率则是66%!)。与之对比,巴菲特1965年—2018年的年均净值增长率为21%、索罗斯旗下量子基金从1969年—2000年的年均收益是32%。


西蒙斯也在2019年,成为财富榜上排名第21位的人。

很多人很好奇大奖章为何如此厉害,它的成功密诀究竟是什么?这也许是个永远的迷,不过,我们也有一些解读这个秘诀的一些线索。

一是美国证券交易委员会(SEC)1988年批准了电子交易,这是实现高频交易的技术基础。

二是从西蒙斯挖人来看,他招了不少之前在IBM via voice项目组做语音识别的人,语音识别与股价涨跌一样,同样是一维的时间序列。因此,预测一维时间序列的波动(即趋势和反转)应该是有意义的。只不过,随着技术的发展,我们应该、也可以使用新的技术(比如神经网络)来预测一维时间序列的波动。

第三个线索则比较有意思。在国内如果我们要挖掘一家公司的秘密,有时候会去裁判文书网搜索。西蒙斯的公司也曾卷入过诉讼。在一起诉讼中,一些商业秘密不得不被批露。两名文艺复兴的前员工表示,文艺复兴公司开发了涉及互换交易的一种策略。他们在法庭文件中将其描述为“大规模骗局”,违反了SEC管理卖空的规定。彭博社曾对此作过报道,现在这篇报道还可以在这里读到。

这里我们不评价文艺复兴的做法是否真正违法。无论如何,规则有漏洞,那是规则的问题。在交易中,找到这些规则的、技术上的“漏洞”(或者交易机会),是高胜率策略的关键之一。


文艺复兴的另一个成功秘诀,可能是所谓的壁虎式交易法则:即交易要像壁虎一样,平时趴在墙上一动不动,蚊子一旦出现就迅速将其吃掉,然后恢复平静,等待下一个机会。不过,如何理解这个法则,不同的人可能会有不同的解读了。

真正指引文艺复兴成功的因素,其实西蒙斯自己介绍过。秘诀就是围绕一些非常优秀的科学家来构建公司,这些科学家是经过相应考核的,公司独特的激励机制,也使得他们能一直跟公司在一起。这个激励机制的核心就是大奖章基金,它只对公司员工开放,而且每个人的薪资主要与整体的表现、而不是个人的表现相关。

如何复制这样一个团队,这可能不仅仅是令国内的机构感到困难的地方,甚至在全球都是这样。这才是文艺复兴真正的护城河,而这种领导力,西蒙斯从在石溪担任数学系主任时就开始构建了。

“不管我在做什么,我总觉得自己像个局外人,”西蒙斯说,“我全神贯注于数学,但我从来没有觉得自己是数学系的一员。我总是有一只脚在那个世界的外面。”

然而,当西蒙斯转身到投资界后,事情刚好反过来,他的一只脚,又踏回了数学界。

1996年,他的儿子保罗车祸遇难。2003年,小儿子尼古拉斯溺水身亡。西蒙斯选择以自己的方式逃避痛苦,开始潜心琢磨那些流传已久悬而未决的数学谜题。“那是避风港,是我心中一个安静的角落”。


2007年,他与苏利文发表了《微分形式普通上同调的公理特征》这篇论文,又杀回了数学界。

文艺复兴和高频量化赚了很多钱。西蒙斯也常常会被问起,高频交易究竟有何意义?每次西蒙斯都会回答,高频交易提供了流动性,使得价格发现更加有效,并且降低了做市商利润,从而使得总体交易成本下降。很难说西蒙斯自己对这个回答有多满意。

可以说,西蒙斯前半生是在追逐利润,后半生则是在追寻人生的意义。带着对人生意义的追寻,西蒙斯开启了慈善家的生涯。


西蒙斯于2004年创立了Math for America协会,旨在促进纽约市中学数学教师的招募。这可能跟他第一次放弃教职,转投军方项目有关。

他解释说,我们通常认为,我们的老师懂数学。你会说当然了。但是很让人吃惊的是,尤其是当你上了中学的时候,你会发现大部分的数学老师数学懂得却不多。其中一种回答就是如果他们真的懂这门学科,那他们可以带着同样多的知识去 Google,Goldman Sachs或者什么其他地方。所以我们必须使这个职位变得更加吸引人,这也就是说给他们发更高的工资,这也正是我们在纽约和几个其他城市通过我们的项目正在做的,给老师们更多的尊重,并提供更多的支持。

西蒙斯还大量捐赠以支持对基础科学的研究,捐赠超过了10亿美元。他提到,如果单从对基础科学的投资规模上讲,应该还没有一个基金的规模能够与我们相比。

2010年,他签署了一项协议,将把他所有的财富,捐给慈善事业。

西蒙斯对中国也有捐赠。清华捐有一栋专家公寓楼,就是他捐赠的,以“陈赛蒙斯”命名。


在西蒙斯逝去时,我想,他更希望被人作为数学家和慈善家被纪念,而不是企业家。这也是他父亲对他的期望。他做到了。

财富扩大了西蒙斯的影响力,数学和慈善则延长了他的生命。

这是西蒙斯在AMS演讲上给出的人生指南:

  • Do something original
  • Be guided by beauty
  • Don't give up easily
  • Hope for good luck.

终极猜想!底蓓离的成因分析

这几天圈内都在传底蓓离什么的。作为严肃的量化自媒体,我们就不跟着吃这波瓜了。不过,我一直很关注技术指标的顶背离和底背离,一直在追问它的成因如何,以及如何预测。

底蓓离把我目光再次吸引到这个领域来,于是突然有了一个猜想。虽然我还没来得及完全证实它,但这个猜想,值得你锁定我的频道。

这个猜想是:RSI的日线顶背离,是因为周线还有上升空间。当周线RSI也达到前期高点时,就会触发回调。此时日线虽然创新高,但高位筹码不稳,回调增多,导致RSI下降。


背离的定义

Investopedia对技术指标的#顶背离定义为:

Quote

背离是指资产价格的走势与技术指标(例如震荡指标)相反,或者与其他数据相反。背离警告当前价格趋势可能正在减弱,并且在某些情况下可能导致价格改变方向。

存在正背离和负背离。正背离表明资产价格可能上涨。负背离表明该资产可能会下跌。

在国内,我们一般使用顶背离和底背离这两个术语。最早大V中较多讨论这两个术语的,可能是炒股养家或者缠中说禅,说明这一现象在实战中是比较受到关注的。

下图中的右图展示了一个日线的顶背离。左图则是对应的周线情况。

附图指标我使用的是RSI,它是一个非常好的震荡指标,深刻反映了当前市场的赚钱效应和强弱力量对比。

在图中,程序自动标注出来了k线的每一个峰和谷。这些标注使用了我们自己的自适应参数算法,在绝大多数时间、无论周期是哪个级别,都能工作得很好。不过这一次,在周线级别上,没能触发它标注出3月22日那一周的高点。


从右图中我们注意到,日线上存在2月27日和3月18日两个峰,后一个峰的价格高于前一峰,但后一个峰的RSI略小于前一峰的RSI(前高78.3,后高77.7),形成了顶背离。

我们在课程中详细介绍过一个独创但更准确地理论,在运用RSI时,不是低于30就会反弹,高于70就会回调,而是要看它跟前一个峰(或者谷)的RSI相比较,如果上涨时,RSI高于前一个峰对应的RSI,则有可能回调。

但这个结论也有自身的问题:一是在图中,日线在2月27日之前,就已经突破了2024年1月25日下降通道中高点RSI,为何一直到2月27日才回调?我们在课程中已经解决了这个问题。


二是为何在2月27日之后,股价还能一直上涨,直到3月18日出现顶背离?这个顶背离问题,我也一直没有思路,但自己着手写了好几个检测顶背离的例程。

猜想和验证

现在我们给出回答第二个问题的一个猜想,即尽管2月27日的日线RSI已经达到高位,但周线RSI仍在低位,它没有表示反对和阻止上涨的意思,因此日线短暂调整后,在其它因素趋动下继续上涨,直到3月22日那一周,盘中突破前高60.5,才引发了一个周线级别的大回调。

Tip

从3月21日起,到3月27日止,中证1000连续回调超过6.94%,从而引发反弹,这是另外一个故事和机会。我们在前面的一篇文章中介绍过,统计数据给出3月27日收盘后,反弹概率超过91.4%的结论,这种情况,应该坚决抄底

RSI是反转指标。它只能以一定概率给出会不会反转,但你不应该指望它给出趋势延续的预测。那是趋势类指标的任务。最重要的趋势类指标,莫过于判断均线的斜率,如果线性回归误差在可接受范围以内的话。在回归误差较大而失效的情况下,课程中也给出了一个非常鲁棒的方法。

我们还是拿数据验证一下:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 日线2月27日、3月18日顶背离,前者RSI 78.3,后者RSI 77.7
dclose = dbars["close"].astype(np.float64)
drsi = ta.RSI(dclose, 6)

for dt in (datetime.date(2024, 2, 27), 
           datetime.date(2024, 3, 18)):
    mask = dbars["frame"] == np.datetime64(dt)
    i = np.flatnonzero(mask)[0]
    print(dt, dclose[i], drsi[i])

# --- output ---
# 2024-02-27 5394.03 78.3
# 2024-03-18 5648.01 77.7

从数据看出,尽管日线价格在上涨,但RSI却下降了,构成了日线顶背离。

接着看周线:

1
2
3
4
5
6
7
8
# 周线rsi前高出现于2023年11月17日,数值为60.5
wclose = wbars["close"].astype(np.float64).copy()

nov_17 = np.datetime64(datetime.date(2023,11, 17))
i = np.flatnonzero(wbars["frame"] == nov_17)[0]

rsi = np.round(ta.RSI(wclose, 6), 1)
rsi[i]

前高为60.5。这里有一个技巧,如果没看明白,可以给我留言:

1
2
3
4
5
6
7
8
9
# 3月22日,周线rsi数值盘中突破 61.9,高于前高60.5,触发回调

mar_22 = np.datetime64(datetime.date(2024, 3, 22))
i = np.flatnonzero(wbars["frame"] == mar_22)[0]

wclose[i] = wbars["high"][i]

rsi = ta.RSI(wclose.astype(np.float64), 6)
rsi[i]

rsi后高为61.9,因此触发回调,并且这个阻力得到了确认。

Tip

注意这里rsi没有到70就回调了。所以,你看到的所有关于RSI的教科书,都该改改了。是不存在 one size fits all。不同的标的、不同的周期以及不同的阶段,应该有不同的参数。这些参数应该可以通过量化程序(或者机器学习)计算出自适应的值。

引申结论及思考

在这个市场上,存在各种不同操作频率(这是FFT和wavelet应该有用的原因)的资金。


对高频量化,他们看的是tick级数据,可能持有几分钟就会调仓;散户和量化多在日线和周线频率上操作,持有数天就调仓;长线资金以季度为单位。越是长线的资金,资金量越大,调仓时对走向的影响越强。

现在,你应该已经猜到了,有一部分资金会在日线RSI高点时撤出;大量的资金会在周线的RSI高点撤出;而更大量的资金会在月线的RSI高点撤出。

但我猜没有资金会根据季线的RSI高点撤出。许多真理,都不能线性外推。

我这么说的原因是,季线资金会按照基本面来进行操作,而不是技术面。有的同学会拿基本面因子与技术面因子揉在一起,无论是多因子回归,还是机器学习,这都是不对的。它们只会相互打架、抵消。

所以,从现在起,你应该给你的股票上个闹钟,计算出它的日线、周线和月线RSI前期高点,然后实时监控这些指标。

一旦三者都达到高点,这轮行情就结束了。如果月线达到高点,日线和周线不在高位,但发生过顶背离,那么,势必在高位产生过滞胀(量在堆积,但上涨幅度不大),此时主力的筹码很多已经被交换掉了。

此时不走很危险。接下来的回调时间,可能以月计。老胡说,“我不割肉,你怎么割我?”但胡锡进终究是等不到那一天。

机器学习(XgBoost)预测顶和底

之前的文章中,我们对中证1000指数进行了顶和底的标注。这一篇我们将利用这份标注数据,实现机器学习预测顶和底,并探讨一些机器学习的原理。

我们选取的特征非常简单--上影线和WR(William's R)的一个变种。选取这两个因子,是基于东吴证券高子剑在2020年6月的一份研报:上下影线,蜡烛好还是威廉好?

他们的结论是,根据这两类指标的变种得到的综合因子,在2009到2020年4月,以全A为样本,进行5组分层多空测试,得到年化收益为15.86%,最大回撤仅为3.68%,可以说具有非常明显的信号意义。


Info

L33 在这次实验中,我们将使用XgBoost。

它的开发者是陈天奇,上海交大ACM班06级毕业,华盛顿大学博士。现任卡内基.梅隆大学助理教授。

除了XgBoost,他还是MXNet的开发者,这个框架是一度与Tensorflow, Pytorch等齐名的四大深度学习框架之一。

题图即为卡内基.梅隆大学校园。陈天奇现在任教于此。

在上一篇文章中,我们提到机器学习总是把要解决的问题归类为两类,一类是回归,一类是分类。如果要预测的target取值处在连续实数域上,这往往是个回归问题;如果target的值域为有限个离散状态,则是一个分类问题。

然而,具体问题总是复杂许多。初学者会觉得,既然股价的取值是在连续实数域上,因此可以把它看成回归问题,使用类似LSTM之类的神经网络来预测股价。但实际上由于金融数据的噪声问题,这么做并没有什么道理。

很可能只有在构建资产定价模型时,才可以当成回归来处理,也就是,根据公司的基本面和宏观经济指标来确定公司的市值,进而推算出股价。这本质上跟预测落杉叽的房价是同样的问题。

如果我们要构建时序方向上的预测信号呢?很可能只能用我这里的方法,不去预测每一个bar的涨跌和价格,而是改为预测顶和底,最终实现买在底部,卖出在顶部。

安装XgBoost

我们一般通过conda来安装它的Python包,但pip(需要版本在21.3以上)也是可以的。

1
conda install -c conda-forge py-xgboost

在Windows上安装时,还需要额外安装VC的分发包。

如果你的机器安装有支持cuda的GPU,那么conda会自动安装带GPU支持的xgboost。

不过,GPU对xgboost的加速并没有对CNN这样的神经网络那么明显。也就是说,即使有GPU,xgboost也只会在某些阶段利用到GPU加速,总体上可能会快几倍而已。考虑到我们的标注数据本身比较小,这个加速并不重要。

数据构造

经过顶底数据标注之后,我们已经获得了一份如下格式的数据:

这份数据包括了标签(即flag一列),但没有我们要的特征工程数据。因此,我们要先从OHLC数据中提取出特征。

我们决定先从最简单的特征提取--上影线和WR(William's R)的一个变种。选取这两个因子,是基于东吴证券高子剑在2020年6月的一份研报:上下影线,蜡烛好还是威廉好?

他们的结论是,根据这两类指标的变种tr得到的综合因子,在2009到2020年4月,以全A为样本,进行5组分层多空测试,得到年化收益为15.86%,最大回撤仅为3.68%,可以说具有非常明显的信号意义。

66%

基于这个基础,我们改用机器学习的方法来做一遍。我们用来提取上下影线和WR的方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def wr_up(bars):
    h, c, l = bars["high"], bars["low"], bars["close"]
    shadow = h - c

    # 技巧:避免产生除零错误,且不影响结果正确
    return shadow/(h - l + 1e-7)

def wr_down(bars):
    h, c, l = bars["high"], bars["low"], bars["close"]
    shadow = c - l
    return shadow/(h - l + 1e-7)

def upper_shadow(bars):
    h, c, l = bars["high"], bars["low"], bars["close"]
    o = bars["open"]
    shadow = h - np.maximum(o, c)
    return shadow/(h - l + 1e-7)

def lower_shadow(bars):
    h, c, l = bars["high"], bars["low"], bars["close"]
    o = bars["open"]
    shadow = np.minimum(o, c) - l
    return shadow/(h - l + 1e-7)

xgboost是基于树模型的,对数据的正则化本来没有要求,不过,为了便于分析和对比,我们对这四个指标都进行了归一化处理,使得数据的取值都在[0,1]之间。

如果是上下影线,值为0.5时,表明影线占了当天振幅的一半高度。如果为1,则当天收T线或者倒T(也称为墓碑线)。

William's R 是美国作家(不要脸一下,就是博主这一类型)、股市投资家拉里.威廉在1973年出版的《我如何赚得一百万》中首先发表的一个振荡类指标,它的公式是:

\[ W\%R = \frac{H_n - C_n}{H_n - L_n} x 100\% \]

计算向下支撑的公式略。

n是区间长度,一般设置为14天。这样\(H_n\)即为14天以来的最高价。其它变量依次类推。如果我们把n设置为1天,就回归成类似于上下影线的一个指标。

与K线上下影计算方法不同之处是,它只使用收盘价,而不是像上下影线那样,使用收盘价与开盘价的最大者(计算上影线时)或者最小者(计算下影线时)。

这里还有一些技巧,比如我们使用了numpy的ufunc之一, maximum来挑选开盘价和收盘价中的最大者。另一个显而易见的方法是:

1
np.select([c>o, o<c], [c, o])

但此处使用ufunc会得到加速。

接下来,我们就可以构建训练数据集了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
data = {
    "label": raw["flag"].values,
    "data": np.vstack(
        (wr_up(bars), 
         wr_down(bars), 
         upper_shadow(bars), 
         lower_shadow(bars)
        )
        ).T
}

bars是numpy structured array, 包含了OHLC数据和flag,由之前的raw变量转换过来。

最终我们生成了一个字典,训练数据存放在"data"下,标签数据存放在"label"下。使用了np.vstack来将特征合并起来。这些函数在《量化交易中的Numpy与Pandas》课程中有讲解。

接下来,我们引入sklearn的中的方法,将上述数据集切分为训练集和测试集,然后进行训练:

1
2
3
4
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = 
                train_test_split(..., test_size=.2)

我们保留了20%的数据作为测试数据。

1
2
3
4
5
bst = XGBClassifier(n_estimators=3, max_depth=2, learning_rate=0.5)
# fit model
bst.fit(X_train, y_train)
# make predictions
preds = bst.predict(X_test)

现在,训练完成,并且我们在测试数据集上进行了预测。接下来,我们希望知道这个模型的有效性。为此我们要引入sklearn.metrics中的一些度量方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from sklearn.metrics import *

acc = accuracy_score(y_test,preds)
print(f"ACC: {acc:.3f}")

recall = recall_score(y_test,preds, average='weighted')
print(f"Recall:{recall:.1%}")

f1 = f1_score(y_test,preds, average='weighted')
print(f"F1-score: {f1:.1%}")

pre = precision_score(y_test,preds, average='weighted')
print(f"Precesion:{pre:.1%}")
mx = confusion_matrix(y_test,preds)

我们得到的结果看上去很完美:

1
2
3
4
ACC: 0.930
Recall:93.0%
F1-score: 89.6%
Precesion:86.5%

但是,这些数据能表明模型真的有效吗?幸福会不会来得太容易?所以,我们还得往下深挖一层,看看实际的预测效果究竟如何。在分析大量样本预测结果时,我们有一个利器,称为困惑矩阵(confusion matrix)。


Tip

关于confusion,有这样一则笑话。在一个选美比赛上,一位美女被要求解释孔子(Confucius)的名言:"Reading without meditating is a useless occupation (学而不思则惘)"。显然这位美女不知道Confucius是谁,于是,她望文生义地猜测, Confucius was one of the men who invented confusion。不过,中庸有时候确实让人confused,猜测孔夫子发明了困惑,也是歪打正着。

我们要将矩阵mx可视化。人类,无论男人还是女人,都是视觉动物。我们无可救药地偏好各种色图。

1
2
sns.heatmap(mx/np.sum(mx), cmap="YlGnBu", 
            annot=True, fmt=".1%")

我们会得到这样一张图:

50%

这张图表明:大约有3.8%的0类数据,被错误分类为标签1;大约有3.2%的2类数据,被错误地分类为标签1;所有的1类数据,都正确地分类为1。

从这张图我们还可以知道,这是一个严重有偏的数据集。但我们最为关注的第0类(对应于flag = -1)和第2类(对应于flag = 1),它没能正确识别。当然,它也没错到把第0类识别为第2类,或者相反。

不过,无论如何,我们有了一个好的开端。随着我们往训练数据里加入更多的数据行、更多的特征,并且使得数据按类别均匀分布后,这个模型一定会有提升。

不过,在改进之前,我们还需要掌握更多关于xgboost及评估指标的理论知识。下期见!

新国九条下,低波动因子重要性提升!

Robert (Bob) Haugen, 低波动因子之父。图片来自MarketWatch


之前我们有一期文章介绍了低换手率因子。它的背后的原理是,要买在无人问津处,藏器待时,最终卖在人声鼎沸时。这是一种博弈逻辑。

今天介绍的低波动因子,同样强调无人问津处的价值。由于它的低波动特性,在实操上,不受短线资金青睐;在学术上,它与CAPM、EMH等流行的理论相悖,因而甫一提出,就被华尔街和学术界视为异类。直到2008年,MSCI才开始涉及这一因子,编制了MSCI全球低波动率指数。

大量的实证研究表明,在长达数十年的投资史中,低波动率因子都具有明显的优势:人迹罕至的道路,有时提供更好的旅程

新国九条之后,红利股在投资中的重要性将大大加强,而低波动因子在发现红利股、白马股方面有优秀的选择能力。这是我们介绍低波动因子的时间背景。

低波动因子的数据表现

从CAPM模型发表以来,华尔街和学术界坚信风险与回报是紧密相连的:市场是有效的,投资者要获得较高的收益,就必须承担更多的风险。

然而,Robert Haugen和他的老师 James Heins 教授在60~70年代就发现,与流行的理论相反,低风险股票实际上能产生更高的回报。

Tip

Robert Haugen, 金融经济学家、量化投资和低波动性投资领域先驱。伊利诺伊大学-香槟分校金融经济学博士,教授。

Haugen认为,有关投资和公司金融的重要教科书“大错特错,需要重写”。由于Haugen对低波动因子的大力推崇,从而获得了“低波动性投资之父”的非正式称号。

Tip

查理.芒格也对现代金融理论报有类似的观点。他曾经讥讽地说,一些把现代金融理论鼓吹得头头是道的诺奖经济学家,不得不把自己管理的基金破产,转而投资伯克希尔.哈撒韦。

根据S&P Global的Tim Edwards博士等人的研究,低波动性指数的风险调整回报在多个国家的表现都超过了母指数,在日本甚至超出近一倍。这个对比图我们不放了,放一个累积收益对比图。

数据来源于S&P Dow Jones Indices, 1990~2018

这个图是低波动因子标的池与市场(标普500)历史累积回报相对比的一个图。从图中可以看出,几乎在任何一个时期,低波动因子的总体收益(而不仅仅是风险调整收益)都超过了市场表现。

他们的构建方法是,从标普500中,挑出波动率最低的20%,并按与波动率成反比的方式进行加权,并且每季度进行一次调仓。

这份研究报告是教育性质的,在jieyu.ai 上提供了免费下载。

MSCI在去年三季度的一篇文章中,披露了他们构建的低波动性指数的表现情况:

图表来源:MSCI网站

上图显示出,低波动因子在市场衰退期反而能逆市上涨,其它多数时间也跑赢指数。

低波动因子的有效性解释

关于低波动性因子有效的原因,学界也是用了很多数学和数据来解释。但实际上,回到问题的本原上来,低波动性是如何造成的?是由于它的投资者长期看好公司,并且以持有吃分红为主,很少交易造成的。

投资者为什么会长期看好一支股票呢?是因为这些公司的业务逻辑很简单(能看懂,所以投资者不会反反复复)、护城河深(竞争格局很难改变)、盈利足够好(核心还是要能赚钱),是那些世界无法改变的公司(摘自但斌近期路演)。

这就有点价值投资的味道了。实际上,学界有人做了很多研究,最后发现,低波动因子与价值因子有较强的关联性:低波动性的股票、往往也是低市净率的股票。因此,白马股、长期红利股,也往往是低波动率的个股。

Tip

既然低波动因子与价值因子强关联,如果一个市场里的财务数据不那么可靠,那么我们就应该使用低波动因子来代替。量价数据永远不说谎。如果一家过去看起来很好的公司,最近出了问题,看财报的人永远是最后一个知道的。但股价会提前反映。

一个有趣的事实是,夏普率与波动率的关系。夏普率是一种风险调整收益率,它的分母--资产收益的标准差,正是波动率的线性函数。

夏普率的提出者,正是CAPM理论的创始人威廉.夏普。而低波动率投资方法的支持者们,正是用低波动率作为武器,举起了反抗以CAPM为代表的现代金融理论的旗帜。如此说来,夏普在发表CAPM模型时,也为这一理论安排了自己的掘墓人。这很辩证法。

波动率的计算

根据investopedia,波动率的公式是:

\[ vol = \sqrt{var(R)}/T \]

这里的T是产生回报率R的周期数。但也有不除以T的做法。

在金融领域,波动率常以年化方式进行呈现和比较。这可以用pandas来计算:

1
2
3
4
5
bars = ...
close = bars["close"]

close.pct_change().rolling(window_size)
     .std() * (252**0.5)

在进行单因子检验时,我们需要求得每一天的标的的波动率,因此会需要这里的rolling版本。

或者,更简单地,使用quantpian的开源库empyrical:

1
2
3
4
from empyrical import annual_volatility

daily_returns = close.pct_change()
annual_volatility

低波动率策略

一般而言,我们无须自行检验低波动率因子的有效性,可以直接使用它来进行选股。

Tip

由于低波动率因子的特性,要检验它,必须要跨越至少一个牛熊经济周期,这样引入的数据量非常大。对A股来讲,我们每天不一样,因此还很难说走过了多少个经济周期。

要注意的是,如果我们以短期的波动率进行选股,非常有可能选中下跌状态中的个股。下图显示了相同的波动率,可以出现完全不同的股价走势:

在低波动率条件下,判断股价走势非常容易,我们对价格进行回归,如果得到的直线斜率大于0,则走势是向上的。

这里的关键点是,我们首先要使用月线,至少24个周期以上。在较短的周期上,低波动率的背后没有经济学上的意义支撑。主要代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
for symbol, name, _ in get_secs():
    bars = get_bars(symbol, 24, ...)
    if len(bars) < 24:
        continue

    close = bars["close"][-24:]
    returns = close[1:]/close[:-1] - 1
    # 计算波动率
    vol = np.std(returns)

    # 计算斜率
    a, b = np.polyfit(np.arange(24), close/close[0], degree=1)
    result.append((name, symbol, vol, a))

df = pd.DataFrame(result, columns=["name", "symbol", "vol", "slope"])

df[df.slope>0].nsmallest(10, "vol")

这样我们就选出了趋势向上,波动率最小的10支。当slope接近0时,实际上还不能认为趋势向上,所以,我们可以通过分位数来进行筛选:

1
2
3
quant_25 = df[df.slope>0].slope.quantile(0.25)

df[df.slope > quant_25].nsmallest(10, "vol")

结束语

人迹罕至的道路,有时提供更好的旅程。新国九条之后,红利股在投资中的重要性将大大加强,而低波动因子在发现红利股、白马股方面有优秀的选择能力。如果一个市场里的财务数据不那么可靠,那么我们就应该使用低波动因子来代替价值因子。量价数据永远不说谎。如果一家过去看起来很好的公司,最近出了问题,看财报的人永远是最后一个知道的。但股价会提前反映。

Don't fly solo! 量化人如何使用AI工具

在投资界,巴菲特与查理.芒格的神仙友谊,是他们财富神话之外的另一段传奇。巴菲特曾这样评价芒格:他用思想的力量拓展了我的视野,让我以火箭的速度,从猩猩进化到人类。

人生何幸能得到一知己。如果没有这样的机缘,在AI时代,至少我们做量化时,可以让AI来伴飞。

这篇文章,分享我用AI的几个小故事。


在讲统计推断方法时,需要介绍分位图(Quantile-Quantile Plot)这种可视化方法人类天生就有很强的通过视觉发现pattern的能力,所以介绍这种可视化方法几乎是不可缺少的。

左偏、正态和右偏分布的QQ图示例

但当时在编这部分教材时,我对QQ-plot的机制还有一点不太清晰:为什么要对相比较的两个随机变量进行排序,再进行绘图?为什么这样绘图如果得到的是一条直线,就意味着两个随机变量强相关?难道不应该是按随机变量发生的时间顺序为序吗?

启用GPT-4的多角色数据科学家扮演

这个问题无人可请教,哪怕我搜遍全网。后来,即使我通过反复实验和推理,已经明白了其中的道理,但毕竟这个知识点似乎无人提及过,心里多少有点不确定。于是,我请教了GPT-4。


最初的几次尝试没有得到我想要的结论,于是,我用了一点技巧,要求GPT-4把自己想像成为数据科学家。并且,为了避免错误,我使用了三个数据科学家进行角色扮演,让A和B分别提出观点,再让C来进行评论,这一次,我得到了非常理想的结果,即使请教人类专家可能亦不过如此。

先给GPT-4提供问题背景:

Quote

Q-Q图的原理是,如果X是一个有序数据集,那么[X, X]在二维平面上的图像一定是一条45度角的直线。

如果我们有随机变量X和设想中的理论分布Y,如果随机变量X服从估计中的理论分布Y,那么就应该有:

在50%分位处,X的采样点\(x_1\)应该与50%分位处的\(y_1\)非常接近(由于是随机变量,所以也很难完全相等);在25%分位处,75%分位处,或者进一步推广,在任意分位数处,两者的采样都非常接近。

在实际绘图中,我们的做法是,假设X服从正态分布,共有n个数据,我们先对X进行z-score变换,然后从标准正态分布中,也随机抽样取n个数,记为Y,对两个数组都进行排序后,对[X, Y]进行绘图,如果它的图形接近于直线,则说明X服从正态分布。

从背景可以看出,我已经掌握了QQ图的大致原理。如果X与Y是同分布的,那么,将X,Y排序后,分别以\([I,X]\)\([I,Y]\)\(I\)为数组坐标)为点绘制的曲线,将会是相似曲线。


但QQ图将其进行了变换,直接取每一个坐标\(I_i\)对应的\(X_i\)\(Y_i\)作为曲线的坐标,从而将只绘制出一条曲线。如果二者同分布,则该曲线将是一条直线。当时我对这个变换的性质还不太了解。

我的问题一共有4个:

Question

  1. 这段话中是否有事实错误?
  2. 如果不对X进行z-score化,这样[X,Y]还会是直线吗?
  3. n值对绘制图形的影响是?
  4. 假设X确实服务正态分布,从标准正态分布中进行抽样得到的Y,是如何保证它与X一一对应的?

最后一问,实际上就是要问这个变换为什么能成立。

GPT-4的角色 -- 数据科学家A发现了背景中第一段的一个错误。\([X,X]\)的图像为45度直线的条件并不需要\(X\)是有序的。任何数据集的\([X,X]\)图像都会是45度直线(显然)。对于第2问,A的回答是:

Quote

If we don't standardize X (i.e., perform a z-score transformation), the Q-Q plot may not form a 45-degree line. The reason we standardize is to put X and Y on the same scale, which makes it easier to compare them.


对于第三问,数据科学家A认为:

Quote

The number of data points (n) can affect the Q-Q plot in terms of its granularity. The more points we have, the smoother and more precise the plot will be. However, even with a small n, if the data follows the theoretical distribution, the points should still fall roughly along the line of equality.

n只影响图像的平滑度和我们判断时的精度。这一点非常准确。但我最关注的是第四位,随机变量X与Y究竟是如何对应的,为什么可以将它们按大小排序?A的回答是:

Quote

Y, sampled from a standard normal distribution, does not necessarily correspond one-to-one with X in terms of specific data points. Instead, it corresponds to X in terms of the order of data points. When we sort X and Y, the smallest value of X is paired with the smallest value of Y, the second smallest with the second smallest, and so on.

答案几乎已经呼之欲出了。即使两个随机变量服从同一分布,它们的值也不会相等,但是,出现在同一位置上的随机变量值,它们的差值会很小。因此,这样绘制出来的图,就是一条45度直线。

B和C主要是对A的结论进行质疑、比较,这里不赘述了。


无论A、B还是C都没有给出最终的解释:为什么如果随机变量X和Y服从同一分步的话,那么在同一位置i处的\(X_i\)\(Y_i\)应该是接近的。但它们确实证实了我们绘制QQ图之前,先对随机变量进行排序的思路是正确的。

Info

关于这一点,应该从CDF/PPF的概念入手来理解。如果\(X\)\(Y\)是同分布的,那么在任一分位\(i\)上,随机变量的值(通过ppf,即cdf的逆函数来计算)都应该非常接近。而排序后的数组,其坐标天然就有了分位数的意义。既然\(X\)\(Y\)在任一坐标\(i\)上都应该接近,那么点\(X_i, Y_i\)就应该落在直线\(y=x\)上。这个变换的作用,是利用人眼对直线更为敏感的现象,把不易分辨的两条曲线相似度的检测,转换成一条曲线是否为直线的检测。

事实上,这一概念在英文wiki上解释的比较清楚。但我当时只看了中文的wiki。

如果上述概念还不好理解,我们可以再举一个更直观的例子。通过QQ图来判断两个证券标的是否存在强相关性。比如,我们以两支同行业个股为例,取它们最近250期日线,计算每日回报率,对其进行排序后绘图:

1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt

r1 = hchj["close"][1:]/hchj["close"][:-1] - 1
r2 = xrhj["close"][1:]/xrhj["close"][:-1] - 1

plt.scatter(sorted(r1), sorted(r2))
x = np.linspace(np.min(r1), np.max(r1), 40)
plt.plot(x,x, '-', color='grey', markersize=1)
plt.text(np.max(r1), np.max(r1), "x=x")

我们将得到如下的分位图:

这就非常直观地显示出,两支个股的走势确实相关:在涨幅4%以下的区域,如果A下跌,那么B也下跌,并且幅度都差不多;如果A上涨,那么B也上涨;幅度也差不多。这正是相关性的含义。这里我们排除了时间,只比较了两个随机变量即日收益率。

Tip

注意看这张图中涨幅大于4%的部分。它意味着,某个标的涨幅大于4%时,另一个标的的上涨幅度没有跟上。这里可能隐藏了潜在的机会。你知道该怎么分析吗?


跟着copilot学编程

有两个版本的copilot。一个是copilot,另一个,现在被叫作github copilot,是vscode中的一个扩展。后者2022年中就发布了,当时有6个月的免费试用期。试用期内一炮而红,迅速开启了收费模式。这也直接导致了同年11月同赛道的工具软件Kite的退出。

现在github copilot每月$10,尽管物有所值,但作为不是每天都coding的人来说,感觉如果能推出按token付费的模式是最好了。

它的两个免费版本,一个是对学生免费。有edu邮箱的可以抓紧在github上申请下。另一个是如果你的开源项目超过1000赞,则有机会申请到免费版。

一般我使用copilot作为编程补充。它在错误处理方面可以做得比我更细腻,另外,在写单元测试用例时(建议每个量化人都坚持这样做),自动补齐测试数据方面是一把好手。

但是我没有想到的是,有一天它还能教我学编程,让我了解了一个从来没有听说过的Python库。

整个事情由ETF期权交割日引起。近年来似乎形成了这样一个规律,每逢期权交割日,A股的波动就特别大,而且以向下波动为主。因此,量化程序需要把这些交割日作为因子纳入交易体系。


但是这些交割日的确定,出入意料地--。它的规则是:

股指期货的交割日为每月的第三周周五;ETF期权交割日为每月第四周的周三;A50交割日为相关月的倒数第二个交易日。

在刚刚过去的4月19日,是一个股指期货交割日。接下来的4月24日,则是ETF交割日。今年的交割日如下:


随手写了几行代码,发现这个日子还是比较难算的。于是打开copilot chat,问它:

Quote

股指期货的交割日为每月的第三周周五;ETF期权交割日为每月第四周的周三;A50交割日为相关月的倒数第二个交易日。

根据上述信息,写一个方法,求出2018年到2024年12月31日止的各个交割日。

Copilot并不知道股市日历,所以无法独立完成上述任务。这并不奇怪。令人惊喜地是,它使用了calendar.monthcalendar这个方法。尽管calendar是一个标准库,但我之前并不知道它还有这个用法。

1
2
3
import calendar

calendar.monthcalendar(2024, 4)

75%


在这个基础上改就容易多了。另一个有趣的例子,是让copilot帮我想函数名。

我喜欢使用这样一个因子,最近20天内,涨幅中位数与跌幅中位数的比率。显然,这个比值越大,说明做多的力量越强。

最初我使用caculate_ratio这个函数名。但几乎所有的因子计算都可以使用这个名字。因此,我让copilot给一个准确点的名字。copilot回答说,根据函数的功能,我们可以将函数名从calculate_ratio改为calculate_downward_exceeding_upward_median_ratio。这个名字更准确地描述了函数的功能:计算下跌幅度超过上涨中位数的比例。

不过,这个名字太长了,也不优雅。于是,我问copilot,名字可以再短一点吗?可以使用metaphore吗?Coplilot回答说:

Quote

当然可以。我们可以使用"bear_bull_ratio"作为函数名。这个名字较短,且使用了金融市场的术语"bear"和"bull",分别代表市场下跌和上涨,这样可以更好地传达函数的含义。

为了机器能学习,我标注了 2 万条行情数据

芝加哥大学海德公园。芝大是经济学重镇,其学者开创了著名的芝加哥经济学派,共产生了 100 位诺奖、10 位菲尔兹奖、4 位图灵奖。今天量化人追逐的 Alpha, 最早就来自于 Michael Jessen 在芝大时的博士论文。


很多人对基于机器学习的量化策略很好奇,常常问什么时候有机器学习的课。其实,对很多人(我自己就是)来说,没有能力改进机器学习的算法和框架,机器学习都是作为黑盒子来学习,难度主要是卡在训练数据上。

这篇文章,将介绍一种数据标注方法和工具。

有监督的机器学习需要标注数据。标注数据一般是一个二维矩阵,其中一列是标签(一般记为 y),其它列是特征(一般记为 X)。训练的过程就是:

$$

fit(x) = WX -> y' \approx y

$$

训练就是通过反向传播来调整权重矩阵\(W\),使之得到的\(y'\)最接近于\(y\)

特征矩阵并不困难。它可以是因子在某个时间点上的取值。但如何标注是一个难题。它实际上反应的是,你如何理解因子与标签之间的逻辑关系:因子究竟是能预测标的未来的价格呢,还是可以预测它未来价格的走势?

应该如何标注数据

前几年有一篇比较火的论文,使用 LSTM 来预测股价。我了解到的一些人工智能与金融结合的硕士专业,还把类似的题目布置给学生练习。


作为练习题无可厚非,但也应该讲清楚,使用 LSTM 来预测股价的荒谬之处:你无法利用充满噪声的时序金融数据,从价格直接推导出下一个价格。

坊间还流传另一个方法,既然数据与标签之间不是逻辑回归的关系,那么我们把标签离散化,使之转换成为一个分类问题。比如,按第二天的涨跌,大于 3%的,归类为大幅上涨;涨跌在 1%到 3%的,归类为小幅上涨。在-1%到 1%的,归类为方向不明。

其实这种方法背后的逻辑仍然是逻辑回归。而且,为什么上涨 2.99%是小幅上涨,上涨 3%就是大幅上涨呢?有人就提出改进方法,在每个类之间加上 gap,即 [-0.5%, 0.5%] 为方向不明,[1%,3%] 为小幅上涨,而处在 [0.5%, 1%] 之间的数据就丢掉,不进行训练。这些技巧在其它领域有时候是有效的,但在量化领域,我认为它仍然不够好。因为原理不对。

我们应该回归问题的本质。要判断每一天的涨跌,其实是有难度的。但如果要判断一段趋势是否结束,则相对来讲,特征会多一点,偶然性会低一点。用数学语言来讲,我们可以把一段 k 线中的顶点标注为 1,底部标注为-1,中间的部分都标注为 0。每一个峰都会有一个谷对应,但中间的点会显著多一些,数据分类不够平衡。在训练时,要做到数据分类平衡,把标签为 0 的部分少取一点即可。


顶底数据的标注

鉴于上面的思考,我做了一个小工具,用来标注行情数据的顶和底。

这个工具要实现的任务是:

  1. 加载一段行情数据,绘制 k 线图
  2. 自动识别这段 k 线中的的顶和底,并在图上标记出来
  3. 把这些顶和底的时间提取出来,放到峰和谷两个编辑框中,供人工纠错
  4. 数据校准后,点击“记录 > 下一组"来标注下一段数据

我们使用 zigzag 库来自动寻找 k 线中的顶和底。相比 scipy.signals 包中的 argrelextrema 和 find_peaks 等方法,zigzag 库中的 peaks_valleys_pivot 方法更适合股价数据 -- 像 find_peaks 这样的方法,要求的数据质量太高了,金融数据的噪声远远超过它的期待。

peaks_valleys_pivot 会自动把首尾的部分也标记成为峰或者谷 -- 这在很多时候会是错误的 -- 因为行情还没走完,尾部的标记还没有固定下来。因此,我们需要手动移除这部分标记。此外,偶尔会发现峰谷标记太密的情况 -- 一般是由于股价波动太厉害,但如果很快得到修复,我们也可以不标记这一部分。这也需要我们手动移除。

最终,我们将行情数据的 OHLC、成交量等数据与顶底标记一起保存起来。最终,我们将得到类似下面的数据:

当然,它只能作为我们训练数据的一个底稿。我们说过,不能直接使用价格数据作为训练数据。我们必须从中提取特征。显然,像 RSI 这样的反转类指标是比较好的特征。


另外,冲高回落、均线切线斜率变化(由正转负意味着见顶,反之意味着见底)、两次冲击高点不过、k 线 pattern 中的早晨之星、黄昏之星(如果你将它们的 k 线进行 resample, 实际上它是一个冲高回落过程,或者说长上影、长下影)等等都是有一定指示性的特征。

标注工具构建方法

Tip

这里我们介绍的是 jupyter 的 ipywidgets 来构建界面的方法。此外,Plotly Dash, streamlit, H2O wave 也是主要为此目标设计的工具。

为了在 notebook 中使用界面元素,我们需要先导入相关的控件:

1
2
3
from ipywidgets import Button, HBox, VBox, Textarea, Layout,Output, Box

from IPython.display import display

在一个单元格中,如果最后的输出是一个对象,那么 notebook 将会直接显示这个对象。如果我们要在一个单元格中显示多个对象,或者,在中间的代码中要显示一些对象,就需要用到 display 这个方法。这是我们上面代码引入 display 的原因。

这里我们引入了 HBox, VBox 和 Box 三个容器类控件,Button, TextArea 这样的功能性控件。


Layout 用来指定控件的样式,比如要指定一个峰值时刻输入框的宽度和高度:

1
2
3
4
5
6
peaks_box = Textarea(
    value='',
    placeholder='请输入峰值时间,每行一个',
    description='峰值时间',
    layout=Layout(width='40%',height='100px')
)

按钮类控件一般需要指定点击时执行的动作,我们通过 on_click 方法,将点击事件和一个事件处理方法相绑定:

1
2
3
4
5
6
7
8
save_button = Button(
    description='存盘'
)
save_button.on_click(save)

def save(c):
    # SAVE DATA TO DISK
    pass
这里要注意的是,事件响应函数(比如这里的 save),在函数签名上一定要带一个参数。否则,当按钮被点击时,事件就无法传导到这个函数中来,并且不会有任何错误提示。

HBox, VBox 用来将子控件按行、列进行排列。比如:

1
2
3
4
5
# K 线图的父容器
figbox = Box(layout=Layout(width="100%"))
inputs = HBox((peaks_box, valleys_box))
buttons = HBox((backward_button, keep_button, save_button, info))
display(VBox((buttons, inputs, figbox)))

Output 控件是比较特殊的一个控件。如果我们在事件响应函数中进行了打印,这些打印是无法像其它单元格中的打印那样,直接输出在单元格下方的。我们必须定义一个 Output 控制,打印的消息将会捕获,并显示在 Output 控件的显示区域中。

1
2
3
4
5
6
7
info = Output(layout=Layout(width="40%"))

def save(c):
    global info
    # DO THE SAVE JOB
    with info:
        print("数据已保存到磁盘!")

与此类似,plotly 绘制的 k 线图,也不能直接显示。我们要通过 go.FigureWidget 来显示 k 线图。

1
2
3
4
5
import plotly.graph_objects as go

figure = ... # draw the candlestick with bars
fig = go.FigureWidget(figure)
figbox.children = (fig, )

我们特别给出这段代码,是要展示更换 k 线图的方法。在初始化时,我们就必须把 figbox 与其它控件一起安排好,但如何更新 figbox 的内容呢?

答案是,让 figbox 成为一个容器,而 go.FigureWidget 成为它的一个子控件。每次要更新 k 线图时,我们生成一个新的 fig 对象,通过figbox.children = (fig, )来替换它。


最后,谈一点 troubleshooting 的方法。所有通过 on_click 方法绑定的事件函数,即使在运行中出了错,也不会有任何提示。因此,我们需要自己捕获错误,再通过 Output 控件来显示错误堆栈:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def log(msg):
    global info

    info.clear_output()
    with info:
        if isinstance(msg, Exception):
            traceback.print_exc(msg)
        else:
            print(msg)

def on_save(b):
    try:
        # DO SOMETHING MAY CAUSE CHAOS
        raise ValueError()
    except Exception as e:
        log(e)

info = Output(layout = Layout(...))
save_button = Button()
save_button.on_click(on_save)

利用这款工具,大概花了两小时,最终我得到了2万条数据,其中顶底标签约1600个。

月亮和Pandas - Wes Mckinney的传奇故事

正如死亡和税收不可避免,Pandas对量化人而言,也具有同样的地位 -- 每个人都不可避免地要与之打交道。而Wes Mckinney正是Pandas的创建者。Pandas是有史以来,最成功的Python库之一,以一已之力,开拓了Python的生存空间。


毛姆的《月亮和六便士》讲述了这样一个故事,一个富有的英国股票经纪人,思特里克兰德,为了追求艺术和美,放弃自己的生活和家庭,来到巴黎,以艺术家的身份过着贫穷却目中无人的充足的生活,最终留下无数画作,享誉世界。

这本书表达了这样的意义,不要因终日寻找地上的六便士,而错过了头顶的月亮。金融和量化界充满着浮躁和虚荣,许多人追逐着快钱,然而easy come, easy go,最终却是一无所获。

20世纪初的金融人思特里克兰德,勘破了俗世红尘,转而潜心研究艺术,尽管后半生在物质上不如之前安逸,但能做自己喜欢的事,才是幸福

我们今天要介绍的Wes Mckinney,其经历也与思特里克兰德有异曲同工之处。不过,Wes Mckinney在经济上只有很小一段时间没有收入,随后很快就取得了事业上的成功。


Wes Mckinney, 大学毕业于MIT,后来拿到了杜克大学的数学和统计学两个博士学位。

他于2007年加入投资管理公司AQR。这是一家巨头公司,员工人数有1000人左右,管理规模达到了1400多亿美元。他们为机构客户提供另类和传统投资工具。

在AQR任职期间,大量的基础数据分析和统计工作是基于Microsoft Excel 手工完成的,这种工作极其繁琐和低效率。于是,Wes Mckinney从2008年开始,尝试开发Pandas,并选中了Python作为开发语言。

来源:pandas logo

在当时Python还不是一种很流行的数据分析和统计语言,不过Wes Mckinney很快就让它流行开来,Pandas在某种意义上,成为了“立王者”的角色 -- 当然Python最终取得今天这般地位,还要借助AI的风力,才能够青云直上。


Pandas非常成功,但带来的问题是,Wes Mckinney的业余时间已经不能满足开发的需求。于是,他像思特里克兰德一样,离开了金融行业(并且退学。似乎每一个成功的美国创业者,都必须有dropout的经历),专心一意开发Pandas。

Pandas是开源产品,最初并不能提供任何收入,Wes Mckinney靠着之前工作的丰厚收入,以及兼职来养活自己。这期间,他还出版了一本名为《Python for Data Analysis》的书,获得了一些稿费。

随着Pandas的成功,第二年,Wes Mckinney就从AQR招募了两名同事,与他一起探索Python和Pandas在金融行业的创业机会,随后, 他和Chang一起创建了DataPad公司。

现在, Pandas由超过2500名贡献者共同维护。 Wes Mckinney不再参与日常的pandas开发。但社区授予他终身仁慈独裁者(BDFL)的荣誉,拥有对Pandas未来发展的决定权,就像Guido之于Python一样。

2016年,Wes Mckinney与他人一起共同创建了Apache Arrow项目,现在Arrow是跨编程语言共享科学数据的主要格式。Arrow仍然是开源的项目,但Wes Mckinney提出一个非营利行业联盟的想法,成功地吸引到Two Sigma(就是大家熟悉的量化投资巨头)作为赞助商,此后,又吸引到NVidia, Intel, bloomberg等大公司的赞助,从而能够支持一个有六名全职开发人员的分布式团队。


从AQR开始,到主要以推广开源数据分析和数据科学工具作为职业生涯的主题,Wes Mckinney 经历了华丽的转身。他现在是Usra Labs的负责人,利用他的声誉,为Usra Labs获得资金,以促使开源和创新持续向前发展。

与坐在办公室里日复一日地数着绿纸相比,他显然更享受现在在田纳西纳什维尔的生活,因为他的目标是为这世界增添一份美好:do as much good in the world as I can。

Wes Mckinney的经历是一个传奇,他的成功也为现在想要进入金融行业的新人提供了一个范例:并不一定亲自下场做为金钱厮杀的角斗士,解决行业在软件方面的痛点,也许更容易成功。这世界并不缺少量化研究员,但Pandas和Arrow的创建者,永远只有一个。

如果你盯着月亮看,最终至少也能获得满天的清辉,它的光芒,远超六便士。

最后是一点来自 Wes Mckinney的小福利。

如果要推荐一本讲解Pandas的书,毫无疑问,没人任何书籍能比《Python for Data Analysis》更权威了。因为它是由Pandas的创建者 Wes McKinney 撰写的!这本书现在提供有网页版供开放访问。读者也可点击此链接阅读。在2023年4月进行更新后,它现在支持到了pandas 2.0版本。


75%

1赔10!中证1000应该这样抄底

3月28日那篇文章分析了前一日的下跌为什么是可能预见的。这一篇文章,我将用坚实的统计数据,说明这一天为什么应该抄底,预期的损益比又是多少。

抄底不是贪婪。抄底是拿先手,以降低风险。在至暗时刻,请铭记,没有最终的成功,也没有致命的失败,最可贵的是继续前进的勇气!


赔率与收益期望计算

3月27日,沪指下跌1.26%,中证1000下跌3.33%,亏钱效应明显。在第14课中,讨论过这样一个问题,沪指下跌4%时,此时抄底成功的概率有多高?我们借这个问题,讨论了PDF/CDF的概念,并且给出了通过statsmodels中的ECDF、numpy/scipy中的相关方法求解的思路。

但在现实中,可能更有实际意义的问题是当A股连续下跌到x%时,此时抄底,预期盈亏比,也就是所谓的赔率会有多大?。刚好,在今年的3月27日,我们就遇到了中证1000收盘时,连续下跌达6.943%的情况,随后连续反弹,上涨达5.5%,收益相当可观。

要解决这个问题所需要的知识,我们在前面的课程中已经做好了所有的知识铺垫。这里面最重要的方法,是在第9课介绍的find_runs。

它的作用是,将数组划分为若干个具有相同连续值的分组,返回这些分组的值、起点和长度。

有了这个方法,我们就可以根据每日收益率,找出每个连续下跌区间的端点,并计算该区间的pnl:

1
2
returns = bars.close.pct_change()
v, s, l = find_runs(returns <= 0)

returns数组是每日收益率。通过returns <= 0表达式,我们将获得一个二值数组,元素为True的部分,意味着当天没有上涨(下跌或者平盘)。

我们将得到如下结果:

结果将是一个三元组的数组,每一个元素由(v, s, l)组成,分别代表当前分组的值、起始位置和长度。

接下来,我们遍历这个数组,根据s和l找到区间的起始点和结束点,再用两个端点的收盘价就可以计算出区间的涨跌。下面的代码中,我们只计算了下跌区间:

1
2
3
4
5
6
7
8
close = bars.close
cum_neg_returns = []
for vi, si, li in zip(v, s, l):
    if vi and li > 1:
        cum_neg_returns.append((bars.frame[si-1], bars.frame[si + li - 1], close[si + li - 1]/close[si-1] - 1))

r = pd.DataFrame(cum_neg_returns, columns=["start", "end", "cnr"])
r

我们得到了116个结果:

50%

在3月27日收盘时,我们所处的位置对应上表中的第114行,也就是从3月20日起到27日,中证1000发生连续下跌,幅度为6.94%。

那么,继续下跌的概率是多少呢?它相当于(s < x).sum()/len(s),我们换一个方法:

1
2
p_decline = r.cnr.le(-0.06943).mean()
p_decline

输出结果将是0.0862,也就是从过去4年的统计数据来看,连续下跌超(含)2天的共有116次。在这116次中,如果收盘跌幅达到6.943%,则继续下跌的概率为8.62%,也就是将有91.38%的概率反弹。

如果不反弹,我们就要蒙受抄底的损失。这个损失的期望是多少呢?我们统计一下,下跌6.94%后,继续下跌的情形是:

1
r[r.cnv < -0.0694]

50%

其预期是:

1
2
3
4
# 抄底失败的亏损预期

exp_lose = (r[r.cnr<-0.06943].mean() - (-0.0694))
exp_lose

抄底失败,也就是继续下跌的情形共有10次,平均损失是-10%左右,考虑到我们是在-6.94%时才抄的底,因此,我们将蒙受3.48%左右的损失。请记住,发生这种情况的概率只有8.62%。

如果反弹,我们将得到多少收益呢?这个计算略困难一点。在3月27日之后,反弹达到了5.5%。我们只有这样一个采样数据,但不能保证每次都会有这么大的反弹。

我们考虑一个保守一点的情况,即把下跌[-5%, -6.943%]间的所有反弹都当成下跌-6.943%以后的反弹。显然,在快速下跌中,下跌越多,反弹就越强,我们这样估算,肯定要比每次下跌-6.94%之后的反弹要低估了。不过,这样也使得我们的模型具有了额外的安全边际。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 抄底时间为bounced
bounced = [f.date() for f in (r[(r.cnr >= -0.0695) & (r.cnr<-0.05)]["end"])]

# 抄底收益为bounced之后,连接上涨的收益

cum_pos_returns = []
for vi, si, li in zip(v, s, l):
    end = tf.day_shift(bars.frame[si], -1)
    if end in bounced:
        cum_pos_returns.append((bars.frame[si-1], bars.frame[si + li - 1], close[si + li - 1]/close[si-1] - 1))
1
2
profit = pd.DataFrame(cum_pos_returns, columns=["start", "end", "profit"])
profit

上述代码先找出反弹的时间,然后遍历之前得到的三元组,如果起始点的前一天是某个连续下跌区间的结束日,我们就计算这一组的收益。最终我们得到的收益预期是:

1
2
3
4
# 抄底的收益期望是

exp_win = profit.profit.mean()
exp_win

这个预期是3.299%。看上去与抄底失败的预期损失差不多,但是,计算赔率时,我们要加上实现概率:

\[ exp\_win * 91.38\% / (exp\_lose * 8.62\%) = 10.03\% \]

最后,我们计算抄底的总体期望为:

\[ exp\_win * 91.38\% + exp\_lose * 8.62\% = 2.71\% \]

如果是DMA,加上4倍杠杆,一次获利将在10%以上。

风浪越大鱼越贵?

几年以前,我的合伙人推荐我看塔力布的《黑天鹅》一书。说实在的,这本书前1/4可以说是封神,但看到后面,感觉有点难以落到实处,无法应用。

图片来自电影《黑天鹅》官宣剧照

题外话: 我更喜欢看娜塔莉.波特曼主演的《黑天鹅》。

不过,我从来没有停止对《黑天鹅》事件的思考。我想,换一个角度,关注黑天鹅事件,但把黑天鹅看成是一种低概率事件来加以研究,也未尝不可。虽然脱了《黑天鹅》一书的窠臼,但好歹也有了一个切入点。

但是,可能有人要问,为什么要去接下跌的飞刀?平平稳稳赚钱不好吗?这是因为,在这个市场,本来就缺少平平稳稳赚钱的机会。

如果不在低位拿到先手,拿到便宜的筹码,就很容易被套在山腰甚至山顶。实际上,如果3月27日你没有入场,那么近期的行情是难以把握的。

抄底!基于坚实的统计概率,拿到先手,立于不败之地,才是在这个市场活下来的不二法门。

额外的惊喜

在我们的计算中,如果抄底失败,我们是按最大损失进行了计提。但实际上,在一轮快速下跌中,下跌越狠,反弹力度就越大,读者可以自行验证。

这里要讲的是,我们对抄底失败,是按平均3%计提的损失。但我们没有计算在被套3%之后,反弹的情况。我们以今年1月25到2月5日下跌最狠的那一轮为例,如果我们在下跌6.94%时就抄底,我们真的会损失3%吗?

读者可以自行验证。实际上,这一轮跌得快,反弹力度也大。如果我们在下跌达到6%以后抄底,大概会买在中证1000的4984点,然后会遭受约13%的下跌,但最终会连续反弹到4993点,最终收益0.18%。

没能上热搜,但卡尼曼值得我们纪念

3月27日,行为经济学的开山鼻祖丹尼尔.卡尼曼去世。作为行为经济学的一个分支,行为金融学在量化中的运用越来越广泛,并成功地解释了时序方向上价格波动的诸多原因。

卡尼曼的重要贡献是建立起了一套形式化方法,使得运用心理学来解释和预测经济行为的研究纳入了科学的轨道。


前一段时间,跟一个学员聊天,他跟我讲,市场可以诞生一切,除了市场本身。这句话意涵很深,又出人意料地简洁,还具有修辞之美。

不是每一个经济学家都能懂得这个道理,但我却从学员那里听到了这样深邃的洞见。在深感荣幸之余,也受到启发:在分享纯粹的量化知识之外,也应该可以分享一些我自已学习经济学的一些思考,也许会有一些读者会觉得有所裨益,正如我从与学员的交流中也得到了启发一样。

正如那位学员所说的:

Quote

我们能够做的,就是把这些经济学的想法分享给身边的朋友,让这些经济学理念不断传播蔓延,那么经过几代人的努力,事情会慢慢有所改观。

是的,这很重要。如果有更多的人关注、学习和研究经济学,就有更多的人通过经济学找回逻辑和常识,那么未来会美好许多。

Be the change you seek!

卡尼曼和行为经济学

卡尼曼是犹太人,出生于特拉维夫,二战时在巴黎,他们一家人被Nazi围捕,不过后来被他父亲的雇主设法施救而逃出生天(另一个辛德勒名单的故事)。


图片来自wiki,遵照cc-by-2.0协议使用

战后他在希伯莱大学学习心理学,并在UC Berkeley获得了心理学博士学位。尽管他获得了诺贝尔经济学奖,但并没有系统地接受过经济学专业的培训。

1979年3月,卡尼曼与合作伙伴特沃斯基一起发表了论文《Prospect Theory: An analysis of decision under risk》,提出了前景理论(也称展望理论、视野理论)。论文发表以来,共获得了超过8万次引用。

从亚当.斯密以来,主流经济学假设人的每个决定都是“经济上理性”的,然而现实情况并非如此;前景理论加入了人们对得失、发生概率高低等条件的不对称心理效用,成功解释了许多看来不理性的现象。

Tip

卡尼曼的好友阿莫斯.特沃斯基于1996年因黑色素瘤去世,撼失诺奖。卡尼曼与特沃斯基有着长达数十年的友谊,共同发表了行为经济学的多篇奠基之作。特沃斯基的妻子曾经说过,他们彼此之间的联系比任何人与其他人的联系都更深,甚至超过了婚姻。事实上,两个人在生活习惯(早起和熬夜)、性格(内向和外向上)大相径庭。两个人密不可分,正如披头士乐队的约翰列侬与麦卡特尼一样。


除了专业领域的建树之外,卡尼曼还是畅销书思考快与慢的作者.

前景理论认为,人在不确定条件下的决策选择,取决于结果与展望(预期、设想)的差距,而非单单结果本身。

假设有两名投顾分别向同一位投资者推销同一个基金。其中第一位在介绍产品时,只强调该基金过去三年的平均回报是10%;而第二位投顾在介绍时,还强调了该基金过去十年的回报率高于市场平均,但过去三年中一直在下降。

前景理论认为,尽管投资者被推销了完全相同的基金,但他很可能会从第一位顾问那里购买。因为人们都有厌恶损失(Loss aversion)的倾向,所以,如果向投资者提供基于潜在收益的投资选择,以及基于潜在损失的投资选择,则投资者将选择前者。

前景理论把决策过程描述为一个函数,并提出如下决策得失函数:

\[ U = \sum_{i=1}^{n} \pi(p_i)v(x_i) \]

这里,\(x_i\)是可能发生的各个结果(它们虽然是随机变量,但服从概率分布,可以认为是客观的),\(p_i\)是这些结果发生的概率。

这里概率权重函数π(p)和价值函数v(x)是前景理论的两个核心函数,都具有非线性的特征。

我们先看价值函数v(x)。

价值函数v是一个非对称的\(S\)形函数,在心理中性参考点之上即收益区间,它是凹函数;而在参考点之下即损失区间,它是凸函数。因此,每一单位的收益(损失)增加,只会得到缩小的收益(损失)价值。换言之,当收益从0到100时主观价值感受,要大于收益从100到200时的主观价值感受。此外,价值函数在损失部分更加陡峭,意味着失去100元的痛苦要大于得到100元的愉悦

图片来源: sciencedirect.com

注意在价值函数里,前景理论开始把客观价值(objective value)与主观价值(subjective value)相区分,见上图中的两个坐标轴。它的重要意义,我们会在文末进行讨论。

概率权重函数\(\pi\)描述了事件的客观概率与该概率的主观投影之间的关系。通俗地讲,低概率事件(比如在异常事故中的死亡率)可能被高估,而高概率事件(比如心脏病或者癌症的死亡率)则被低估。它同样被表示为一个非线性(sigmoid)类型的函数。但是,它的形状需要在具体的博弈事件中进行统计。

下面的例子具体说明了前景理论的应用。假设承保风险的概率为 1%,潜在损失为 1,000 美元,保费为 15 美元。如果我们把参考点设置为当前的财富,那么:


  1. 支付$15的保险,其前景效用为v(-15),注意,这里并不是-15,而是它的一个函数。
  2. 不购买保险,这样一旦意外发生(1%的概率),将会损失$1000;或者以99%概率损失0。此时的前景效用为:
\[ \pi(0.01) \times v(-1000) + \pi(0.99) \times v(0) = \\ \pi(0.01) \times v(-1000) \]

最关键的部分来了。根据前景理论: 1. \(\pi(0.01) > 0.01\),因为低概率会被过度加权。 2. \(v(-15)/v(-1000) > 0.015\),由损失中价值函数的凸性决定(如果是线性,则左右两边相等)。

由上述1), 2)可推导出:

\[ \pi(0.01) \times v(-1000) < v(-15) \]

也就是,由于对小概率事件的过度重视,会使得我们在评估一个客观上只会以1%概率发生的事件时,把它当成超过1%的事件,从而最终的损失期望将大于-15美金,从而使得我们倾向于购买保险。

被低估的行为经济学

卡尼曼的去世,在中文世界里似乎没有引起太大的反响。看到有一些纪念文章,更多地是把他当成《思考快与慢》的作者来纪念。


思考,快与慢。图片来自douban

卡尼曼的重要性来自于他的开创性。他不仅是将心理学运用在经济领域研究当中,更重要地是给出了这种研究的形式化方法。我相信,在他们之前,也一定有不少人分析经济活动中的心理学归因,但由于缺乏形式化方法和数学模型,这些研究都无法纳入科学的轨道。

卡尼曼和特沃斯基的成功之处,在于使用数学语言为行为经济学打下了一个基础框架,从而使得后来像塞勒等人可以继续这一领域的研究。

另一个重要性是,从亚当.斯密以来,理性经济人一直是经济学中的重要假设。尽管大家都明白从这一假设是有局限的,但一旦破除了这一假设,经济学应该如何向前推进,一直缺乏有效地示范。行为经济学在它的这一小块领域中,成功地摒弃了理性经济人假设,引入了主观价值的概念,并使得主观价值的计算成为可能。

经济学最基础的问题之一,就是价值的主观与客观之分。

一些理论认为,价值是凝聚在商品中的无差别人类劳动,因而商品的价值是客观的。商品的生产时间越长、越复杂,它的价值就越高,而与后面的交换过程完全无关(卖不出去的商品,与卖得出去的商品,只要生产所用的时间一样,它们的价值就相同)。


它确实能解释许多经济现象,但是不能解释这样的现象:生产1万砘水泥,会产生一万吨水泥的价值;但生成1亿吨水泥(2021年全球产量为44亿吨),不但不产生任何价值,反而会造成生态灾难。当然就更不用说如何解释人类在美容、旅游、艺术品等方面的经济行为了。

主观价值理论(subjective theory of value)则认为商品之价值并非由于内在的客观属性或生产它的必要劳动等客观事物所决定,而是由行动着的个体对商品实现期望目标之作用大小所决定。

本质上,主观价值理论是一种以人为本的理论,你需要的,才是有价值的

主观价值论能解释更普遍的经济现象。比如,它能解释同样是一坨屎,对屎壳郎会是精美的粮食,具有重要的价值,而对于牛来讲,这样“有价值”的东西,牛并没有生产它,并没有花费任何必要的社会劳动时间--它只是纯粹的排泄物。这是客观价值理论无法解释的现象之一。

从这个意义上讲,如果要像物理学领域那样建立起经济学领域的大一统理论,从主观价值理论的公设出发,更有可能得到结果。

尽管行为经济学只在经济活动中的一小段过程中(价值交换)引入了主观价值,而且还保留着客观价值的定义,但是,这可能是人类第一次通过形式化方法计算主观价值,其背后的意义之重要,自不待言。这可能是行为经济学真正被低估的地方。

交割日魔咒?

周日莫斯科的恐袭,让所有的A股交易者捏了一把汗,怕不是我A又要买单?果然,短短三日,沪指跌去1.8%,中证1000跌去5.89%,亏钱效应还是非常明显的。

痛定之后,留下几个复盘问题,首先是,下跌的原因是什么?当然,我们求解的方法,都是量化思路。

交割日魔咒?

关于下跌的原因,最被人采信的说法是今天是ETF期权交割日。上一个交割日,2月28日,沪指大跌1.91%。

顺便,我们也把今年的几个重要的交割日分享一下:

股指期货的交割日为每月的第三周周五;ETF期权交割日为每月第四周的周三;A50交割日为相关月的倒数第二个交易日。


作为量化研究员(或者quant developer),我们需要自己有能力计算出上述交割日。

Tip

在量化二十四课中,这个问题是作为一道练习题出现的(有参考答案)。不过,这里我们可以介绍一下解题思路。

核心是要使用calendar这个库。我们可以通过它的monthcalendar来构建每月的日历:

1
calendar.monthcalendar(2024, 2)

这会得到以下结果:

表格中为0的部分,表明那一天不属于当月。因此,我们要知道每月的周五,只需要将上述输出转换为DataFrame,再取第4列,就可以得到所有的周五了。然后判断第三个周五是否为交易日,如果不是,则取第四个周五。

大摩的预言

另一个传闻则是一个未经证实的大摩的小作文,说是反弹到3090点以来,A股获得超过15%,可以看成兑现2024年利润预期。

实际上,这应该是一则谣言,原因是,除了今天之外,本周大小摩一直在加仓。

趋势分析方法

在量化二十四课中,我们介绍了一些趋势分析方法。实际上,运用这些方法,我们不需要去猜测大跌背后的直接原因;相反地,近期A股的走势已经预示了下跌的可能,随后的测试确认了下跌趋势成立。

首先是3100整数关口压力。我们在《左数效应 整数关口与光折射》那篇文章中介绍过左数效应和整数关口压力/支撑。上周二、周四确认了压力存在。

其次是对RSI的分析。本轮日线RSI的高点在2月23日打出,随后分别在3月5日、3月11日和3月18日形成顶背离。这期间在30分钟级别上,RSI得到多次调整,但在3月21日上午10点,30分钟的RSI也再次形成顶背离。在课程中,我们介绍RSI超过前期高点,是一个值得注意的见顶信号,如果此时均线是走平或者向下的,则基本可以确认。

如何寻找RSI的前期高点呢?我们介绍的方法是,通过zigzag库,寻找5日均线的上一个最高点,则它对应的RSI也就是前期的高点。下一次当RSI在数值上逼近或者超过前高时,也往往就是局部的高点。为什么不用zigzag来寻找本次的最高点?这是因为zigzag寻找高低点需要延时几个周期才能找到。如果我们使用移动平均线的话,还要再多几个周期的延时,这样等zigzag确认最近的高点已经出现时,往往已错过了最好的交易时机。改用RSI是否逼近前期高点,我们可以立即发出信号,并且在下一周期,就可以根据k线走势来确认这个信号是否得到市场认可。

通过zigzag来寻找局部最高点的方法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from zigzag import peak_valley_pivots
close = ...
ma = moving_average(close, 5)

pct_change = ma[1:]/ma[:-1] - 1
std = np.std(pct_change)

up_thresh, down_thresh = 2*std, -2*std

peak_valley_pivots(ma, up_thresh, down_thresh)

peak_valley_pivots需要我们传入两个threshold参数,以帮助它来确认一个局部极值是否可以作为峰谷。只有当该点大于左右侧up_thresh,down_thresh以上时,一个点才会被确认为峰。对谷的确认也是一样。

这里我们用了一点小小的统计技巧,即使用近期涨跌幅的2倍标准差来作为阈值。

如果一个点高于平均值的两倍标准差,也确实可以看作是一个离群值 -- 从几何意义上来讲,可以当成一个峰值或者谷值。这种处理方法,我们在布林带、或者seaborn的包络图中已经见识过了。

通过上述方法,在绝大多数情况下,我们可以很准确地找出过去出现的峰和谷:

75%

通过这些峰和谷,我们就能找出上一次RSI的最高点和最低点。当下一次RSI再次逼近这个值的时候,我们就可以发出信号,并且在下一周期检测信号质量。

最后是对均线拐头的判断。在上周五10时后,30分钟线的20周期均线已经拐头,且股价运行在均线下方,结合前面的整数关口、RSI高点,此时可以立即减仓。

随后在3月25日下午14时,三个30分钟线确认无法突破这条下行的均线,确认反弹失败。此时的20周期线就象一个屋顶一样盖下来。我们把这种均线的压力称为穹顶压力。传统的一些经典技术分析中,也把它当成抛物线来拟合和分析。在量化二十四课中,我们介绍了一种更鲁棒的方法来判断均线的走势。毕竟,多项式拟合在多数情况下是不可用的。

方法的有效性分析

我们这里讲到的整数关口、穹顶压力和RSI前高,都有精准的定义,是完全可以量化的。读者也可以自行观察它们的有效性。

你可能要问,如果这些方法有效,那岂不是制造出来一台印钞机?

实际上,印钞机要复杂许多。戏法人人会变,各有巧妙不同。要做到实时跟踪这些信号,你得有一个稳定、高性能的量化框架。

其次,这里的方法更适用于大盘分析,但投资最终是要落实到个股上的。只有能选取出跟大盘走势高度相关的个股,大盘分析才能发挥作用。

此外,尽管我们使用了多种方法来提前捕捉信号,但信号发出到确认仍然有一个延时。这会使得我们逃顶和抄底都会晚一些,从而损失部分利润。如果趋势频繁翻转,这种情况下,利润损失加上交易成本,也有可能失败。但是,它显然能帮我们避免大的损失。

量化分析的作用不是保证你百分之百成功,但它能保证在应该成功的时候,你比别人更有可能成功。

资产全球配置

在A股下跌比较确定时,我们应该及时退出市场。也可以考虑一些跨境ETF。在这今年以来的行情中表现的特别明显。比如今天盘中沪指14点开始反弹时,某跨境ETF从高点下跌1.6%;而当14:15,沪指上攻到30分钟20周期均线遇阻后,该ETF立刻反弹,最后半小时上涨1.37%,最终收报3.67%。在一些极端时刻,这些跨境ETF与沪指形成了翘翘板效应。