跳转至




2024

ORB! Alpha 达到年化 36%

这个策略来自 Carlo Zarattini 等人,在 Quantpedia 2023 年大赛中获第三名。他们研究了最近 6 年的美股市场,发现聚焦在热门股 (Stocks in Play) 中时,5 分钟 ORB 策略实现了超过 1600%的总净收益,夏普高达 2.81,年化 Alpha 为 36%。同期标普只有 198%。

这个策略简单暴力,只利用了量比、ATR 和 5 分钟突破等非常简单的技巧。数年以前,有个朋友通过机器学习,得到过一个两周翻倍的策略,我一直没好意思问他实现细节。但通过他的调仓记录来看,最终实现的效果与此庶几近之。


ORB 策略最早由 Toby Crabel 于 1990 年提出,他在《Day trading with short term price patterns and opening range breakout》一书中,详细地介绍了这个策略及系统实现。此后,无论是产业界还是学界都在持续探索和改进这个策略。

2023 年,Carlo Zarattini 等人在 ORB 的基础上,加入了 stocks in play 的限制,将策略的夏普提升到 2.81,年化 Alpha 则达到惊人的 36%(扣除交易费用,参考巴菲特的 20%左右,大奖章的 39%左右)。

选股过滤器

Zarattini 等人提出的策略有以下过滤器:

  1. 开盘价要超过$5。
  2. 过去 14 天平均成交量超过 1 百万股
  3. 14 天 ATR 要超过$0.5
  4. 量比至少为 2,并且
  5. 只交易量比为前 20 的个股

交易执行

根据开盘前 5 分钟的方向,确定当日交易方向。比如,如果开盘头 5 分钟为阳线,则考虑在该股上做多;反之做空。如果头 5 分钟为阳线,即使后面是向下突破,也不考虑做空。


在上图中(注意红色为阴线、空心为阳线),个股以阴线开盘,因此当天的交易方向确定为做空。10 点,股价向下突破(超过头 5 分钟最低价),开空仓,并在 10% ATR 处下止损单。当日尾盘卖出。

下图则是另外两个例子,分别显示了在做多和做空情况下,何时开仓。左图的例子特别提示了,并不是每一次开仓都能获利,仍然有风险因素要考虑。


75%

关于这个策略,如果你还在期待我讲更多,很遗憾。我讲完了。

大道至简

请原谅我用了一句割韭菜的老师们常用的一句话。不过,毕竟咱们是做量化的,高低也得整几句理论的。接下来,就是见证数学公式和金融理论的时刻。

Why it works?

Zarattin 策略成功的关键不是 ORB 策略本身,而是通过条件过滤器,筛选出来了一个独特的小宇宙 (universe)。这个小宇宙的居民,都是高能粒子,有足够的波动性。

另外,由于有美股 T+0 和做空的特性,可以实现斩断亏损,让利润奔跑(通过 ATR 及仓位控制,让每支股票即使亏损,对组合的冲击也控制在 1%以内)。


Tip

A 股没有 T0 和个股做空手段(不是所有的券都能融到),但并不意味着这个策略不能在 A 股应用。

我们逐一解释一下过滤器中的条件。

股价大于$5,不用多解释,可怜之人,亦必有可恨之处。低价股便宜总有便宜的理由。

过去 14 天平均成交量超过 1 百万股。这一条尽管有多种解释,但它在短线操作中的作用,就是要保证成交活跃,这样交易时才不会有太大的冲击成本。

14 天 ATR 超过 0.5,是要保证股性活跃。ATR 是一个绝对值,我们也可以用 20 天均线值转换成百分比,这样可以得到一个适合所有标的的 ATR 标准。

第 4 和第 5 条是选股器的战斗机! 量比是关键,是信号发射器,只此一手,选出热门股 (stock in play)。第 4 条主要是保证在整体市场清淡的情况下,还能保证选出的个股满足要求。

如果你是一些割韭菜号的读者,你很可能听他们这样讲早盘选股策略:

Quote

集合竞价时,把量比前 10 的个股加入自选,观察到 10:50,上涨且涨幅不超过 3%,下探时不破分时均线的,买!


如果这些策略没有一定的合理性,是不可能割到韭菜的。

Zarattin 等基于大量的数据统计,发现了量比与盈利能力(含多空双边)之间的统计相关性,证明了选股老司机的理论有合理性。见下图:

这里的量比就是大家理解的量比,为严谨起见,我们也把他们的计算公式也发布出来:

\[ RelativeVolume_{t,j} = \frac{ORVolume_{t,j}}{\frac{1}{14}\sum_{i=1}^{14}ORVolume_{t-i,j}} \]

这里\(ORVolume_{t,j}\)是个股 \(j\)\(t\) 日前 5 分钟的成交量,\(ORVolume_{t-i,j}\)则是个股\(j\)\(t-i\)日前 5 分钟的成交量。


从数据上看,只选择量比超过 30 倍的个股,看起来性价比更高。但根据 Zarattin,这样实际上会减少一年之中的交易机会,进而减少了总盈利。

除了基于统计数据之外,我更关心策略背后的经济和博弈原理。量比放大究竟意味着什么?如果搞清楚这一点,我们就能知道这一关键因子会不会失效,如果会,何时失效。

用好一个因子,比挖掘新的因子更重要。因为大道至简,趋动股票上涨的因素,说破天,也无非就是那么几种。

在有经验的短线交易者之间,一直流传着只做热门股的策略。一家公司可能因为这些信息的披露而成为热门股:

  1. 超预期的收益报告披露或者预披露。
  2. 专利、药品、许可的批准或者撤销。
  3. 重组和收购或者建立战略联盟、伙伴关系。
  4. 重要产品发布、拿下重要合同(或者在竞标中失去)或者客户。
  5. 管理层变动
  6. 股票拆分(本文作者:即送转)、回购、发债
  7. 突破关键技术点位。

除第 7 点之外,其背后都蕴含着公司盈利能力变动的信息。因此,一旦这些信息被公布,往往就会引来众人的关注,提醒他们去交易该股。无论这些信息是在盘中发布的、还是盘后发布的,人们在晚上都有充分的时间来接收、消化这些信息,并且为第二天早间的操作做出决策。


因此,如果某家公司在头一天有 breaking news 放出来,第二天的成交量就一定会比前一天大许多;反过来,如果一支股票的成交量突然放大,则往往也意味着该公司出现了超预期的消息。

如何预测这些消息对方向决定作用呢?分析师往往是屁股指挥脑袋,别听他们的。也别自己分析,因为股票这个事,你分析正确没用,只要其他人都分析错了,你分析正确也白搭。

ORB 策略告诉我们,看早盘 5 分钟的方向。如果是阳线,大概率是做多,如果是阴线,大概率是做空。

股市是赤裸裸的金钱民主。大家用钱来决定方向,非常真诚。

为什么要强调量比?因为如果做多时成交量没有明显放大,意味着交易的人仍然是场内资金,场内资金往往会做骗线。只有大量的场外资金涌入时,金钱博弈出来的方向才是真实的方向。

如果有人想改变这个方向,他就得拿出更多的钱来。他想高调,但实力他也不允许啊。这样确定的方向,像极了渣男的爱情,它可能短暂,但每一次都很真诚。

现在,你还要用大模型来进行文本挖掘,进行预测吗?还是开始相信,一切信号,都蕴藏在量价之中?

百闻不如一练。在大富翁量化环境中,我们提供了2005年以来的分钟级数据,回测支持到分钟,你可以亲自己试一下这个策略。


Tip

写给随机游走和 EMH 学说爱好者。这两个学说在股票市场上没有一席之地(但在期货和期权上是正确的)。

熬夜的人看不到早间新闻。市场主体接收、消化以及响应这些信息的时间绝不可能同步。

公司经营所得的利润总在源源不断地注入股市,股市总的来说正方向的(当然不包括缅甸)。

它前进的步伐在大时间跨度上,会大于 GDP 的增长速度,因为股市里聚集的应该是一国最好的公司。

在 A 股的应用

要做到文章所说的收益率和低风险,需要满足两个条件,一是要能 T0 止损。第二个则与市场无关:你需要有一套量化交易系统,能快速计算出全市场 5 分钟量比,能够快速下单,一键止损。原文虽然提到了做空,但仅做多也是 OK 的。

但在 A 股,我们无法做到 T0 止损,这会放大一些风险。如果可能,可以考虑转债品种。

原文(PDF)还研究了不同时间窗口(10, 15, 30 等)下的 ORB 策略,并且提供了加量比约束与不加量比约束的策略对比。需要原文的读者请转发本文后索取。

高薪金领都用啥编程语言?SQL、Python领航,附排名!

最近两天,收到私信咨询,想进入量化领域难吗?

提问者没有介绍任何自己的背景,也没有明确说明具体要从事什么岗位。因此这是一个无法回答的问题。不过,我会持续跟进这个问题,并给出一些参考资料。

今日焦点:金融界最需要什么样的编程技能。数据来源于Revelio Labs和eFinacialCareers。前者是人力情报数据分析公司,后者是金融专业求职平台。


根据Revelio Labs 的数据,在金融服务业最受欢迎的编程语言前十排名如下:

你可能想不到,SQL在金融领域有着王者般的地位。在整个科技领域,SQL与岗位的相关性只占18%,但在金融招聘领域中,却有25%左右的岗位要求掌握SQL。

当然,也有许多人并不把SQL视为编程语言。老实说,SQL只能视为一种数据库查询语言,我们无法用它来完成数据查询之外的工作。

因此,Python才是真正意义上的王者,不仅仅是在金融领域,根据TIOBE 6月的排名,它仍然位居榜首,并且受欢迎程度在上升中。


但是,金融行业对C++和Rust这样的互联网热门编程语言的需求并不大,尽管这两种语言在高频交易中不可或缺,但毕竟高频交易比较小众、无法吸纳大资金,因此行业的重心不会在这里。

越是低频交易,越能吸纳大量资金。因此,像Python这样尽管性能不佳,但开发快速灵活的语言,在金融界被广泛使用。此外,SQL的大量使用,也证明了金融业对大数据处理能力的要求并不高,大量的数据处理场景仍然可以使用SQL来实现。这也是完全可以理解的,因为在中低频交易广泛使用的财务数据,数据量并不大。

Java位列三甲也是意料之中。大量的事务系统,包括公司网站仍然会使用Java来开发。


Javascript能上榜,很可能也是因为这样的用途。投资公司为了保持神秘和高科技形象,他们的网页也常常做的比较酷炫。比如Millennium(千禧年)的官网上,就常常使用Javascript炫技。在他们最新的主页上,展示了js制作的磁力线效果和各种reveal特效。

千禧官网

这界面丑是丑了点。但理工男的形象是立住了。所以,前端做得好,也是有机会进金融行业的。

R排在第4名也不意外。R语言在数据科学领域被广泛使用,R语言天然有很多统计方案的模块,它的语法简单,支持管道操作,对于临时性的数据分析处理非常友好。SAS上榜的原因也是一样。SAS与R对因子分析都天然支持得很好。统计模块及因子分析,都是金融行业数据分析的基础。


比较令人意外的是VBA。实际上VBA上榜,蕴含了在金融行业必须熟练掌握Excel的意味。所谓熟练掌握Excel,决不是仅仅是指掌握了它基于图形界面的功能,而是要求在关键时刻、复杂功能及快速处理大量数据时,能够使用VBA来编写脚本。

具有强大的编程能力,对于金融领域求职有多重要?我们看一下eFinancial Careers最近发布的一个全球信贷和可转换债券主管岗位,这是一个年薪$50万到$80万之间的工作:

Quote

A renowned investment firm is looking for an Head of global credit and convertible bonds to report to their CIO function ...

Experience

  • Fixed income in preferably a sell-side company
  • Front-office
  • Experience with arbitrage
  • Experience with credit, equities, convertibles, CDS
  • Strong programming skills (Python or R, SQL, Excel or VB)
  • Strong communication skills and ability to work in a team

如果你是一名在校生,可能多少掌握一点SQL和Python,但究竟要达到什么样的程度才算精通呢?

关于Python,在工程技巧方面可以参考我的新书《Python高效编程实战指南》(预定7月开售),算法方面可以多刷leetcode、kaggle的题,或者简街、千禧的puzzle专栏,我们的专栏也不时会有一些性能优化的技巧。

50%

关于SQL,如果是开发岗,需要了解数据分区规划、索引优化、SQL优化等,如果是数据分析岗,则只需要了解如何构建复杂的查询即可。这些查询主要是窗口函数应用、子查询与自连接、复杂条件筛选与分组等。

反抗者的崛起!Fawce 和 Quantopian 的量化之路

2008 年的金融危机后,华尔街开始围绕人工智能、算法策略和海量数据构建美丽新世界。掌握编程和算法的大拿是这个新世界的宠儿。也有一批反叛者,他们拒绝华尔街的征召:宁愿穿着睡衣在书房里工作,也不愿在豪华大楼的格子间正襟危坐。

Quantopian 正是这一群人的领袖。John Fawcett(昵称 Fawce)则是这家公司的头儿。

对量化萌新来说,John Fawcett 的名字略显生疏。这篇文章,我们将起底 John Fawcett,了解他从四大天坑专业成功转型量化金融的历程,并和他一起,追问量化事业的意义。

Fawcett 出生于一个普通的家庭。他的父母在退休之前,基本上都要同时打 3~4 份工来维持生计。尽管如此,他的父母坚持把子女的教育放在第一位,供他上了哈佛。他还有一个哥哥,后来选择了教育事业,成为了一名中学校长。这样的出身和家庭背景,对他的理念形成产生了重要的影响。

Fawcett 本科毕业于哈佛,专业是四大天坑之一的材料学。在千禧年前后的.com 泡沫白热化期间,他爱上了编程,于是搬到旧金山,为 Scient(一家软件咨询公司)工作。在这里,他管理美国职业棒球联盟的视频编码项目。

L50

John Fawcett 的量化之路起源于爱情的指引。他在 Scient 的事业很顺利,但他的女朋友在波士顿上医学院,于是,他又回到了东部,并在一家对冲基金找到了一份分析师的工作。Fawcett 发现这时的自己还并不擅长资产管理,他的工作是为投资经理编写软件,使得信息的收集和分析得以自动化。

很快,Fawcett 就离开了这家公司,并且创建了 Tamale Software。这家公司开发了用于基本面分析的软件。客户是对冲基金和资产管理公司。Fawcett 很快就取得了成功,2008 年,他把公司以 7000 万美元的对价,卖给了 Advent,这是一家位于旧金山的为投资者提供软件和服务的公司。Fawcett 继续为 Tamale 工作了一段时间,在这期间,Advent 强大的销售队伍,给 Tamale 带来了新的资源,比如新加坡主权基金、来自挪威和美国各地的基金经理成为他的客户。

在这段期间,他还接触了大量的在物理学、计算化学、信号处理领域的博士,对他们在市场方面独特的看法所吸引:即通过下钻到海量数据,建立模型来挖掘未发现的投资机会。

同时,他也了解到,即使这些人有很强的编程和算法能力,也对量化交易非常感兴趣,但由于之前没有投资经验,他们不得不被挡在传统的金融机构门外。

Fawcett 决心改变这种状态,给金融袪魅,实现量化投资过程的平民化,要为业余交易者--包括从来自康奈尔大学的计算机学生,到为互联网公司工作的高级数据科学家,再到机械工程师--为他们提供进入量化领域的机会,这是 Fawcett 曾经幸运地拥有、但不是所有人都能得到的机会。

要有光。

于是,他构建了 Quantopian。在 Quantopian,他们为社区提供海量数据和量化工具、甚至是昂贵的回测平台(指运行时所需的计算资源)。任何人都能免费加入这个社区,通过 Python 和 Notebook 构建自己的投资策略,并进行纸面上的投资。

一旦某人的策略在 6 个月的实时模拟交易中胜出,他就将获得一笔 10 万美元的基金管理权,正式开始实盘交易。如果继续成功,他将获得平台提供的高达 5000 万美元的支持。社区的另一职能则是为未来宽客提供教育资源。

Quantopian 的想法迅速吸引了大量的宽客:航空航天工程师、消费品公司的增长分析师、数据科学家、系统工程师、黑客、和博士生。在高峰时期,Quantopian 聚集了 12 万名会员,并且得到了像 Point 72 这样的投资巨头的认可,从他们那儿得到了 2.5 亿美金的资管规模。不仅如此,Quantopian 的商业模式不仅在美国、甚至在全球都被模仿,被福布斯评为美国最有前途的公司之一。

但是,也许是 Quantopian 的名字带着某种宿命式的悲剧色彩,也许是 Quantopian 的运营模式并不完美 -- 实时数据和回测引擎的运营费用真的得很贵,但Quantopian并不能从这些资源中得到多少回馈。尽管 Fawce 承诺要为所有人提供一个实现他们梦想的机会,但 Quantopian 自己最终却活在了梦想的光环之外。在运营 9 年之后,2020 年 11 月 14 日终止了运营。

Tip

Quantopian 的名字来源于 Quant 和 Utopia(乌托邦)。这表明,Fawce 在创建 Quantopian 之初,就带着某种改变量化交易的理想。但这个名字,也无疑一开始就暗含了某种悲剧色彩。

在 Quantopian 终止运营之后,许多用户猛然发现,自己在社区中留下了大量的 Notebook、想法、互动和回忆。

一位用户写道,“我一生中从未见过有这么多聪明人愿意与我分享如此多的信息而不求任何回报。我现在并将永远感激你们为我所做的一切”。

另一位用户则说,“在 2016 年加入后的几个月内,我学会了 Python,编写了所有基于 Excel 的策略,参加并赢得了第 22 场比赛,并且开始在 IB 进行实时交易。之前的我没有相关学位和任何经验,但现在我正在面试纽约的大项基金。Q 改变了我的生活。”

Quantopian 的失败,只是商业上的失败。在实现金融平民化的愿景上,Quantopian 已经取得了很大的成功。今天,Quantopian 开发并开源的量化工具,无论是 zipline, 还是 pyfolio 或者 Alphalens 等等,都已经成为某种事实上的标准。

如果没有这些软件,很难想像,一个业余的量化人、或者规模较小的机构该如何起步:传统的金融人并不太熟悉编程、而纯粹的计算机专业人士则不太理解金融理论。Quantopian 把因子检验、资产组合管理、回测等量化必备的方法,以接近开箱即用的方式,教给所有人。

Quantopian提供的教育资源和平台更是帮助了许多人成功转型。

L50

在Quantopian的网站上,Fawcett分享了赛义德·拉赫曼的转型故事。赛义德出生在一个股市受到污名化,股市经常与赌博联系在一起的国家。他在本科期间接受了时间序列分析和数据科学的训练,并通过互联网攻读了 CFA 1 级。进一步深造在他的国家是不可能的,但去美国学习是一个对他难以抉择的重大决定,更不用说由于缺乏奖学金带来的财务障碍(在美国读金融真是太贵了!)。幸运地是,在他读研究生期间,发现了 Quantopian。对于赛义德来说,这个平台不仅仅是一个工具,更是知识和机会的灯塔。正是在这里,赛义德不仅磨练了自己的技能,还找到了灵感和社区。

赛义德在一次分享中,具体地谈到了社区的经验贴与教科书的重大不同:

Quote

Max Margenot from your team once in his webinars went through OU (Ornstein–Uhlenbeck process - a stochastic differential equation that's used in understanding mean-reverting process), those were things that one normally finds in textbooks or expensive programs like CQF, but here Max was explaining it using Python, and as a student trying to wrap my head around the practical applications of these complex topics, the content your team released was just pure gold. 大意:奥恩斯坦-乌伦贝克随机过程在教科书和CQF这样昂贵的课程中,都只介绍了理论,但Max Margenot在webinar中分享了如何通过Python来实现这样复杂深奥的模型,这些内容弥足珍贵。

滋养了赛义德的,不仅仅来自Quantopian的教育资源。2017 年和 2018 年的 QuantCon 更是成为他的重要舞台,为他打开了社交、学习和曝光的大门。

在 2017 年的演讲上,他关于强化学习在量化方面的探索得到了许多人的关注,最终他于次年加入杰富瑞(Jefferies Group)的数据科学团队。

成功转型之后的赛义德也开源了自己的框架:MBATS 和 Cloud-MBATS,这两个框架专为在外汇现货和股票市场进行回测和实施基于机器学习的策略而设计。

这种分享行为植根于他自己的经历和 Qantopian 的影响,反映了赛义德对量化交易知识和工具平民化的信念,也成为金融平民化理念逐渐被人接受的一个例证。

这也正是 Fawce 的愿景。“我们的使命是打破量化圈的封闭,使得它能对所有人开放”。Fawce 在 Quantopian 网站上的一篇博客中这样写道,“众包阿尔法是一个登月计划,但 quantopian 成功地在宇宙中留下了痕迹,并让量化金融界的很大一部分了解我们并使用我们的工具”

Quantopian 和赛义德等人的工作,为下一代宽客,特别是那些来自贫困背景、可能无法轻松获得此类资源的宽客提供了支持。如果没有 Quantopian 发出的号召,这些人的生活不会有机会改变。

Quantopian 旅程的结束并不意味着 Fawce 对金融平民化探索的终止。在 Quantopian 终止运营后,Fawce 和 Quantopian 一起合并进入了 Robinhood。这是一家主要面向散户的股票和加密货币经纪公司,在网上提供完全免费的服务。Robinhood 更是旗帜鲜明在把“democratize finance for all”写入了公司愿景。

Tip

Robinhood 是中世纪英国民间传说中的侠盗,是一位劫富济贫、行侠仗义、亦正亦邪的绿林英雄。一般译作罗宾汉。Robinhood公司使用这样一个名字,别有深义。

Robinhood 到 2022 年 4 月止,拥有 2280 万个账户和 1590 万个活跃用户,是一个更大的社区。这不是结束,也许反而是涓涓细流汇聚成河,是另一个探索的开始。

加入 Robinhood 之后的 Fawce 很少高调活动。不过,Quantopian 的老用户惊喜地发现,在 2023 年底,借着感恩节的时机,Quantopian 重启了社区教育,对每位在校学生免费开放。只要你的大学在university-domain-list 当中,就可以免费注册并获得量化课程。

这也许是为了回应 Quantopian 关闭之后社区的呼声,以及他内心深处的价值观。在他的博客中,Fawce 写到,我哥哥的教育事业对我来说也是一种启发。我们的父母把我们的教育放在第一位。Quantopian 背后的大部分理念,即对机会和教育的奉献,来自我的父母和我的兄弟。将教育视为如此深刻的价值观也让我非常感激听到 Quantopian 为人民教育服务的故事。

结束语

在编写关于因子分析与机器策略的课程的过程中,无意中发现了 Fawce 的故事。受同样的理念激励的,不仅仅是 Quantopian, Fawce, Saeed,也包括分布在世界上其他地方的人,包括我自己。于是我决定暂停写作,花几天时间挖掘一下 Fawce 的故事,并和大家分享。

因为知道因何而战,永远比战斗本身更重要。

给所有量化人、和准备进入量化行业的人。

Barra风险模型构建完全指南

Barra风险模型是业内最有名的多因子模型之一。它最初由Barra Inc(创始人Barr Rosenberg)提出,后来被MSCI(明晟)收购,因此现在是MSCI的资产。在MSCI网站上,我们还可以看到名为BarraOne的产品推介。

很多想学习Barra模型的人,不知道Barra有一个官方的完全指南(《Barra Risk Model Handbook》),全文共204页,系统地阐述了如何构建Barra风险模型,是最权威的一本指南。

Barr Rosenberg大学主攻的是文学(UC Berkeley),后来才转攻经济学。Everbody can quant,只要你想。


关于Barra模型,常见的误解之一就是它是用来预测的投资模型。但实际上,它只是风险归因分析模型。关于这一点,最权威的说法无疑来自Barra自己的介绍:

Quote

Of course, MFMs have their limitations. They predict much, but not all, of portfolio risk. In addition, they predict risk, not return; investors must choose the investment strategies themselves.

手册先用10页左右的篇幅解释了什么是多因子模型,以及相关的数学基础。

Tip

关于因子分析的数学基础,可以参考Peter Tryfos(约克大学教授)的《商业分析与预测方法》中的第14章,讲得深入浅出。

在第二和第三部分,先是回顾了从马科维茨到CAPM以及APT,然后介绍了Barrar 权益多因子模型:

50%



下载

暴力美学!洗盘模式如何检测?

无洗盘,不拉升。 筹码收集阶段,股价呈现出上涨形态,也吸引到许多不坚定的跟风盘,它们将成为主升过程中的不利因素。

因此,在拉升之前,主力会采用洗盘的方式,将这些不坚定的低价筹码洗下车。这个过程中往往暴涨暴跌,犹如一匹烈马,要摆脱它身上的骑手一样。

暴力洗盘,某种程度上就成为了行情快速上涨之前的信号之一。

这篇文章,我们量化实现的技术问题:如何快速检测出洗盘模式?


定义

L50

暴力洗盘是在证券市场上观察到的一种经验模式,因此没有严格的定义。一般把两阳夹一阴、且涨跌幅都巨大的情况认为是暴力洗盘。在本文中我们把两阳夹两阴、且涨跌幅都较大的情况定义为暴力洗盘。但我们介绍的方法,也完全适用于其它模式,只需要微调参数即可。

如左图所示,标的在1号位置之前,经过一段时间的吸筹,由于期间股价上涨,已经吸引了一些跟风盘。主力在1号位置拉出20cm,在这一过程中,较多跟风筹码被锁定在涨停位置。

第2天起主力开始洗盘,连续两天,分别下跌14.4%和18.9%。此时在1号位置买入的筹码因为忍受不住巨大的跌幅,忍痛交出筹码。主力筹码增加,成本降低,为后面的拉升留出了空间。

第4天主力将个股拉涨9.4%,表明洗盘结束。

随后几天的整理,主要是留出时间,让下一波的跟风盘有时间发现这支标的,并且有信心跟随买入。紧接着使用一系列小阳线做出上升趋势,最终再拉出一个20cm,从第4天起,短期涨幅高达87%。


我们为什么要使用两阳夹两阴的4天模式来定义洗盘?

因为经过两天的洗盘,从时间和空间上看,洗盘效果会更好(考虑到交易者心理因素,一些人第一天亏损后,往往还不会绝望,第二天继续下跌,更容易崩溃卖出)。另外,从一些技术指标上来看,经过连续两天大幅下跌,技术指标修复比较到位,也更能为后面的拉升腾出上涨空间。

我们为涨跌幅设置一个阈值,如果期间的每个bar的涨跌幅超过这个阈值,我们就认为发生了洗盘。在我们的示例中,使用的阈值是0.05,即涨跌5%。

下面我们来看代码实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 示例1
def feature_washout(bars, threshold=0.05):
    """返回在bars中最后一次洗盘结束的位置,-1表示最后一个bar,
        0表示不存在洗盘模式
    """
    close = bars["close"]
    opn = bars["open"]
    truerange = np.maximum(np.abs(close[1:] - close[:-1]), 
                           np.abs(opn-close)[1:]) 
    # 百分比化
    tr = truerange / close[:-1]
    sign = (opn < close)[1:] * 2 - 1
    signed_tr = tr * sign

这里我们使用了truerange这个变量名,是因为这段代码脱胎于技术指标TR

这段代码解决如何将涨跌幅转换为由1,-1和0表示的模式,以便我们后面进行模式检索。

如果当天涨跌超过5%,或者实体的振幅超过5%,我们就将其标记为1或者-1,否则标记为0。标记的符号由它的形态是阴线还是阳线决定。阴线为-1,阳线为1.

我们通过这样一段简单的代码就实现了求阴阳线的功能:

1
(opn < close) * 2 -1

其结果将生成由1和-1组成的数组。无论是涨还是跌,我们总是认为,阴线是洗盘。所以,高开大阴线,即使收盘是上涨的,我们也当成洗盘来处理。

下图就是高开大阴线洗盘一例:

75%


在判断每个bar的涨跌幅、或者实体的振幅是否超过阈值时,我们使用了一个简单的技巧,即通过np.maximimum来从多个数组中,以 element-wise 的形式选取最大值。即,如果有数组\(A\)\(B\),那么\(np.maximum(A, B)\)将返回一个数组,其元素为\(A\)\(B\)对应位置的元素中的较大值。

也就是,如果结果是\(C\),那么\(C_0\)将是\(A_0\)\(B_0\)中的较大值,\(C_1\)将是\(A_1\)\(B_1\)中的较大值,以此类推。

除了使用\(np.maximimum\)这种 ufunc 之外,实际上\(np.max\)也可以用来完成这项任务,只是我们需要先将数组\(A\)\(B\)堆叠成一个矩阵:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 示例2
A = np.arange(4)
B = np.arange(3, 7)
C = np.arange(8, 4, -1)

Z = np.vstack((A,B,C))

# 通过np.max求每列最大值
r1 = np.max(Z, axis=0)

# 通过np.maximum求最大值
r2 = np.maximum.reduce([A, B, C])

# 比较两种方法的结果是否相同
np.array_equal(r1, r2)

为了提供更多信息,示例中我们演示了三个数组按元素求最大值的情况。答案是要使用reduce方法。如果只在两个数组间进行比较,则可以只使用np.maximum

经过示例1处理后,我们可能得到如下所示的数组:

[ ... 0.04 -0.02 -0.06 0.04 -0.04 -0. 0.2 -0.14 -0.19 0.09 -0.03 ...]

显然,我们还应该把它二值化,转换成为[大阳,大阴,大阴,大阳](即[1, -1, -1, 1])这样的模式:

1
2
3
4
5
6
7
8
9
# 示例3
encoded = np.select([signed_tr > threshold, 
                    signed_tr < -threshold], 
                    [1, -1], 0)

for i in range(len(encoded) - 3, 0, -1):
    if np.array_equal([-1, -1, 1], encoded[i:i+3]):
        return i - len(encoded) + 2
return 0

我们通过select方法完成了二值化转换。接下来我们通过一个逆序的循环,通过array_equal完成了模式匹配。

在回测中,我们可能需要一次性地提取出很长一段行情中所有的洗盘模式,并对其效果进行检验。上面的代码还可以通过numpy.lib.stride_tricks.sliding_window_view进行优化:


1
2
3
4
5
6
7
8
def feature_washout(bars):
    ...
    washouts = []
    for i, patten in enumerate(sliding_window_view(encoded, window_shape = 4)):
        if np.array_equal(patten, [1, -1, -1, 1]):
            washouts.append(i)

    return washouts

通过将涨跌幅二值化,我们就可以在随后方便地通过array_equal来匹配模式。我们这样做,是因为在这里定性分析基本就够了,只要涨跌幅超过5%,那么无论是跌了5.1%还是7.2%,我们都认为是洗盘。

但是,如果你觉得定量分析仍然有意义,也可以通过求皮尔逊相关系数的方法来进行模式匹配。

不过,被匹配的模式,应该使用多少呢?如果你对此感兴趣,可以评论区留言,获得推荐参数。

前视偏差 - 看似明白,实则糊涂

之前有一篇关于数据标注的笔记,得到了较多关注,也引起了一些同学质疑:

你使用了zigzag函数,这不会引起未来数据吗?

未来数据更学术化的说法叫前视偏差(look-ahead bias),回测中引入了未来数据,确实是做量化中很容易犯的一个错误。

这篇笔记解释了zigzag为什么用在标注中,不会产生未来数据。我们还列举了一些其它前视偏差的例子。这些是量化的经验之谈,来自实践,很少能在论文中看到。

Zigzag与 peak_and_valley_pivots

在之前的笔记中,我提到了使用zigzag函数来寻找k线中的波峰与波谷,并作为监督学习的标注数据。如果波峰记为1,波谷记为-1,其它的都记为0,则我们一共生成了三类标签。

这个标注数据还包含了原始的k线数据。因此,你可以基于这个数据集,构造自己的特征数据来训练模型,使之能预测一段行情的顶和底。

构建特征数据(因子)时,可以用RSI、均线拐头、上下影线、成交量放大、整数位等支撑、压力指标、压力线、支撑线等等。

数据标注使用zigzag包里的peak_and_valley_pivots函数。这个包可以通过以下命令进行安装:

1
pip install zigzag

peak_valley_pivots接受三个参数,第一个是要检测峰谷的时间序列,第二个参数用来设置下跌的幅度、第三个参数用来设置上涨的幅度。

也就是说,在序列\([t_0, t_1, t_2]\)中,如果\(th_1\)是下跌阈值,\(th_2\)是上涨阈值,则当 \(t_1 >= t_0 \times (1 + th_2)\) 并且 \(t_2 <= t_1 \times (1 + th_1)\)时,\(t_1\)就会被标记为波峰,如下图所示:

在Python中,还存在其它许多类似的库,最著名的是来自scipy.signals模块下的 find_peaks, find_peaks_cwt, argrelextrema 等。这些函数都要求我们至少设置判断峰谷的涨跌阈值(绝对值),有的还会允许设置驻点窗口大小。但是,zigzag 包中的peak_valley_pivots 方便之处在于,它不是通过绝对值来设置阈值,而是通过输入一个百分比来设置 -- 这是我们更常遇到的场景。

Tip

数字信号处理(Digital Signal Processing)在量化上有特殊的用途。文艺复兴早期在招人时,就很看重具有数字信号处理经验的人,他们从IBM viavoice团队挖了不少人。早期的声音识别是典型的数字信号处理应用,现在才全部转为深度学习。像傅立叶变换、小波变换等技巧,至今仍有不少论文在研究。

在具体使用peak_valley_pivots时,有两个简单的技巧。

首先,如何设置涨跌阈值?在实践中,我们常常先求得时间序列的每期涨跌幅。对股价波动而言,这是一个近似于平稳的序列。对这个序列求其标准差,±2倍标准差就可以作为阈值。

如果股价的涨跌近似于正态分布,那么如果t1超过t0达到2倍标准差的概率应该小于5%,把它认为是一个Outlier(离群值)显然是合适的(注意这里有很多近似处理的地方。严格来讲,股价涨跌近似于正态分布,并且股价超过平均值2倍标准差,概率才会小于5%)-- 换句话说,它就是我们要找的峰(或者谷)。

另一个技巧是,你必须理解peak_valley_pivots是如何处理头尾的数据的。

为什么说“zigzag会引起前视偏差”?

无论如何,peak_valley_pivots都在序列索引为0和-1的位置处,返回峰(1)或者谷(-1)的标签,无论这些点上的涨跌是否达到了阈值。这也是我们这篇笔记讨论前视偏差的起点。

我们再回顾一次之前的标注图。这一次,我们加入了一些序号以方便讨论。

在这个图中,peak_valley_pivots 对位置1和位置2都给出了标注,分别是-1和1。

但是,我们的标注工具没有显示位置2的标注。因为此时这个标注并不能固定下来。当我们投喂更多的数据时,我们发现,在位置2处的标注消失了(指peak_valley_pivots的返回结果,而非图中的显示),直到4月6日下午14:30分,peak_valley_pivots 才重新给出了标注(见位置3)。

这是我们在标注历史数据时会发生的情况。如果我们的标注工具是一次性地对一个非常长的序列进行的标注,那么我们将只会在头和尾遇到一次这样的问题(⚠️⚠️但是,此时阈值会在这个序列的全生命期上进行计算,这个阈值将会与只在近期较短一段时间上计算出来的不一致)。

在对数据进行分批标注时,数据被截断,从而引入了更多的头和尾,在这些边界上, peak_valley_pivots都给出标注,但往往是不稳定的(对开头而言,它很可能是错的或者多出来的)。但从倒数第二个标注起,它就已经被固定下来,不会变更。

这样会引起未来数据吗?

当然不会。这个标注过程完全正确。

⚠️但一些同学使用zigzag,并不是用来做数据标注的,而是用它来做回测的,这就会出现问题。⚠️

这个回测错误具体地说是这样产生的:在\(t_2\)时间点时,检测到了发生在\(t_1\)时间点上的一个峰,此时,如果按\(t_1\)的价格去卖出:

  1. 如果回测系统是正确的,模拟交易不可能完成。如果能够完成,意味着股价又返身向上了,此时在\(t1\)和现在的时间点\(t_0\)之间,已经出现一个谷底。这意味着我们把该持有的股票,错误地卖出了。
  2. 有的同学可能使用自己写的回测系统,比如,通过pandas dataframe来做向量回测,此时就非常容易引入错误,导致在\(t_0\)时间点时,回到\(t_1\)时间点,以该点时的价格,将股票卖出在最高点。

这是一个很容易理解,但又常常会隐藏在你的代码中的错误 -- 如果你不了解回测系统的运行原理的话。

Tip

既然zigzag的顶底标注不能用来预测,那么它还有用吗?答案是肯定的。虽然我们不能用zigzag来预测顶底,但是,它可以用来标注历史数据,以验证我们找出来的关于顶和底的特征是否属于高概率特征。

如果是,那么在实盘中,当这些特征再次出现时,我们就敢于预测,一个顶或者底出现了。

前视偏差常见的场景

产生前视偏差的场景很多。一般来说,它往往产生于数据的采集和发布过程,或者产生于加工处理方法。

比如,财务数据很容易产生前视偏差。财务数据常常以季度为单位进行“索引”。很自然地,财务数据的编制和发布时间要晚于这个“索引”时间。

以2019年一季度美国的宏观经济数据发布为例。GDP预估值发布于2019年4月25日;5月30日进行第一次修正;最终修正则是在当年的6月27日。这是正常和可预料的情况。

Tip

在回测和实盘中如何避免这种前视偏差?在我们的《量化二十四课》的第22课有详细说明。

财务造假也会造成前视偏差。举一个A股中真实的例子。某海产品养殖公司,2014,2015年连续两年亏损被ST,如果2016年再亏损,按当时规则将进入退市流程。于是,2017年3月,该公司公布了2016年年报,净利润7571万,扭亏为盈,成功保壳。2019年,最终查实他们虚增利润1.3亿,追溯修订2016年财报为亏损-5543万。

如果我们现在(2024年)运行回测,由于这些数据已经得到了修正,当回测运行到2017年3月,我们取得的财报数据将是-5543万,这会帮助我们在回测中避开这个雷;但在2017年5月,实盘中获取到的该公司上年净利润为7571万。但你的模型在实盘中无法避开这种雷。

其它引起前视偏差的典型场景还有:

  • 前复权
  • 引用错误
  • 偷价
  • 统计函数如max、min、rank等,也包括zigzag类的函数

除了前视偏差,我们在回测中还可能遇到幸存者偏差、过度拟合、冲击成本、交易容量不足、回测时长不足、交易规则错误等各种偏差。这些都是只有在实战中才能获得的经验,我们在《量化二十四课》中都有介绍,可以帮你迅速提升经验值,由量化萌新成长为量化高手。

你也可以继续关注本号,我们后续会有机会深入讲解这些概念。

封面故事

封面是石溪大学的Charles & Wang中心,由美籍华人、亿万富翁、CA科技创始人王嘉廉捐助修建,是一座帮助人们了解亚洲文化和世界其它文化相互作用的建筑。

该建筑由砖块和白色半透明玻璃组成,象征着中国传统建筑中的窗楹蒙纸,此外还有一座带台阶的拱桥,让人联想到中国的寺庙。其内部有一个供学生使用的东亚美食广场,以便让人了解东亚、特别是中国的美食文化。

石溪大学与华人很有缘。杨振宁在此执教37年。丘成桐在此担任过助教。陈省身则是石溪的荣誉博士。

石溪大学出了很多名人,共拿了至少8个诺奖。量化教父James Simons则曾任该校数学系主任。

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

周六早上醒来,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")

结束语

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