暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

以 Alpha 101 为例:量化因子的多范式计算方法

1267

在投研阶段,研究员通常需要通过历史数据批量计算生成因子,一般做法是将每一种因子的计算都封装成自定义函数。但因子类型多种多样,每一名研究员的编程习惯也不尽相同。

选择合适的方式计算各种金融因子,能降低编写复杂度,提高投研效率,同时还能为后续的流批一体交易、团队因子代码提交和管理,以及搭建统一运行的因子计算作业框架,起到至关重要的作用。

今天我们将以 Alpha#1Alpha#98 这两个复杂因子为例,为大家介绍两种因子计算方式:
  • 面板模式:自定义函数的参数一般为向量,矩阵或表,输出一般为向量,矩阵或表。

  • SQL 模式:自定义函数的参数一般为向量(列),输出一般为向量。

面板模式

更接近数学公式的直观表达


面板数据

panel data

是以时间为索引,标的为列,指标作为内容的一种数据载体,它非常适用于以标的集合为单位的指标计算。将数据以面板作为载体,可以大大简化脚本的复杂度,通常最后的计算表达式可以从原始的数学公式中一对一翻译过来。
像 Alpha#1、Alpha#98 这种,既要基于历史时序数据做计算,又涉及横截面计算的复杂因子,用面板数据直接进行计算,会更容易接近因子数学计算公式本身的表达方式,脚本会更直观,我们在写函数时也会方便许多。
我们通常可以通过 panel 函数,或者 exec 搭配 pivot by 得到需要的面板数据,其中,每一行是一个时间点,每一列是一个股票。在面板数据上,由于是以时间为索引、标的为列,因子可以方便地在截面上做各类运算。


Alpha#1 因子计算(面板模式)


    @state
    def alpha1TS(close){
        return mimax(pow(iif(ratios(close) - 1 < 0, mstd(ratios(close) - 1, 20),close), 2.0), 5)
    }
    def alpha1Panel(close){
        return rowRank(X=alpha1TS(close), percent=true) - 0.5 //rowRank:在每一个时间截面对标的排名
    }
    raw_data_alpha1 = select * from loadTable("dfs://k_minute_level","k_minute") where date(tradetime) between 2020.02.01 : 2020.02.28
    input_panel_alpha1 = exec close from raw_data_alpha1 pivot by tradetime, securityid
    alpha1_panel_res = alpha1Panel(input_panel_alpha1)

    在这段代码中,exec
    搭配pivot by
    得到了矩阵input_panel_alpha1
    ,作为函数的入参。

    DolphinDB 内置的 rowRank 函数可以在面板数据中的每一个时间截面对各标的进行排名;iif 条件运算可以在标的向量层面直接筛选及计算;mimax 及 mstd 等滑动窗口函数也是在标的层面垂直计算的。

    可以看到,各种不同的计算维度都能在面板模式下轻松实现。
    输出结果也是面板数据形式,由此直接用于策略的研发和计算将非常方便:


    Alpha#98 因子计算(面板模式)


      def prepareDataForDDBPanel(raw_data, start_time, end_time){
          t = select tradetime,securityid, vwap,vol,open from raw_data where date(tradetime) between start_time : end_time
          return dict(`vwap`open`vol, panel(t.tradetime, t.securityid, [t.vwap, t.open, t.vol]))
      }
      @state
      def alpha98Panel(vwap, open, vol){
          return rowRank(X=mavg(mcorr(vwap, msum(mavg(vol, 5), 26), 5), 1..7),percent=true) - rowRank(X=mavg(mrank(9 - mimin(mcorr(rowRank(X=open,percent=true), rowRank(X=mavg(vol, 15),percent=true), 21), 9), true, 7), 1..8),percent=true)
      }
      raw_data_alpha98 = loadTable("dfs://k_minute_level","k_minute")
      start_time = 2020.02.01
      end_time = 2020.02.28
      input_panel_alpha98 = prepareDataForDDBPanel(raw_data_alpha98, start_time, end_time)
      alpha98DDBPanel = alpha98Panel(input_panel_alpha98.vwap, input_panel_alpha98.open, input_panel_alpha98.vol)

      Alpha#98 因子同时使用了三个面板数据,分别是 vwap, open 和 vol。

      各矩阵内部运用了 rowRank 函数进行横向截面运算,以及 m 系列函数进行垂直滑动窗口计算,各矩阵之间也进行了二元运算。最后仅用一行代码就解决了多维度的复杂的嵌套计算逻辑。

      输出结果如下所示:

      我们知道,基于面板数据的因子计算,耗时主要在面板数据准备因子计算两个阶段。

      在很多场景下,面板数据准备的耗时可能超过因子计算本身。为解决这个问题,我们要学会灵活使用 DolphinDB 提供的 TSDB 引擎宽表存储,即把面板数据直接存储在数据库表中(面板中每一个列存储为表中的每一个列),这样通过 SQL 查询可以直接获取面板数据,而不需要通过转置行列来获取,从而大大缩短准备面板数据的时间。
      此外,DolphinDB 针对面板数据处理提供了一系列优化过的内置函数,例如 m 系列tm 系列等窗口计算函数row 系列行计算函数等,可以帮助用户以更少的代码量、更快的速度完成因子计算。

      对于日频等低频数据,我们推荐大家使用面板模式,结合 DolphinDB 的一系列优化函数,实现既直观、又高效的因子计算。


      其实,面板模式在 Python、Matlab 等一些专门用于数据分析的工具中也都可以实现,但其计算过程主要基于内存运算也就是说,随着数据频率上升,数据量增加,面板模式对分钟频、快照、逐笔等中高频数据的处理会变得吃力——为此,掌握 DolphinDB 的 SQL 写法就非常重要。

      SQL

      先来聊聊 SQL

      提到数据的存储和查询,SQL 语句就好比分析师手里的瑞士军刀——操作便捷且用途多样。通过 SQL 语句,我们可以高效地创建、读取、更新和删除数据库中的数据,并通过内置的函数和操作,例如聚合、排序和连接等,对大量数据进行快速整合与简单分析。

      DolphinDB 对 SQL 的支持,搭配上分布式系统,体现出了以下优势:

      易用性

      SQL 是一种通用且被广泛接受的数据查询语言。许多开发者和数据分析师都熟悉 SQL,因此无需学习新的查询语言。

      高效查询

      由于 DolphinDB 原生分布式,支持自动解析 SQL 任务,可分发到多个节点上并行处理,显著提升大数据量下的查询速度。

      横向扩展

      当数据增长或查询负载增加时,可以轻松地添加更多的节点到分布式系统中,以满足增加的需求,而无需对现有的系统进行重大更改。

      容错性

      在分布式系统中,如果某个节点发生故障,其他节点可以接管工作。这确保了故障情况下的高可用性和数据的持续访问。
      如果将标准 SQL 语言与函数式编程融合,我们就可以直接使用GROUP BY 和聚合函数(如SUM(), AVG(),MAX()等),在几行代码内就实现对每只股票的分组处理,同时通过结合不同的聚合函数和条件子句(如WHERE,HAVING),轻松筛选和计算复杂因子值。

      对于分钟频、快照、逐笔等中高频数据,我们推荐大家使用 SQL 模式,结合 DolphinDB 的分区存储方案,高效实现库内并行计算。


      SQL模式

      高频因子分布式并行计算

      我们先来看一个简单的例子。我们想计算4000只股票2020年2月份因子 factorDoubleEMA 的分钟值,计算逻辑包括一个无状态自定义函数 sum_diff,和有状态函数 ema(exponential moving average)。
        def sum_diff(x, y){
        return (x-y)/(x+y)
        }


        ema(1000 * sum_diff(ema(price, 20), ema(price, 40)),10) - ema(1000 * sum_diff(ema(price, 20), ema(price, 40)), 20)

        模拟原始数据长下面这样:

        表中 close 一列为收盘价格,就是我们需要用到的作为向量化函数的入参。

        我们可以直接在 SQL 中通过 context by 子句按股票代码分组,然后在 select 中调用 factorDoubleEMA 因子计算函数,来计算每个股票在一段时间里的因子值。

          @state
          def factorDoubleEMA(price){
          ema_2 = ema(price, 2) //ema:在给定长度(以元素个数衡量)的滑动窗口内,计算 X 的指数移动平均(Exponential Moving Average)。
          ema_4 = ema(price, 4)
          sum_diff_1000 = 1000 * sum_diff(ema_2, ema_4)
          return ema(sum_diff_1000, 2) - ema(sum_diff_1000, 3)
          }
          res = select tradetime, securityid, `doubleEMA as factorname, factorDoubleEMA(close) as val from loadTable("dfs://k_minute_level","k_minute") where date(tradetime) between 2020.02.01 : 2020.02.28 context by securityid

          值得注意的是,context by 是 DolphinDB SQL 对 group by 的扩展,是 DolphinDB 特有的 SQL 语句。group by 只适用于聚合计算,也就是说输入长度为n,输出长度是1。context by 适用于向量计算,输入长度是n,输出长度也是n。

          factorDoubleEMA 因子输出结果

          复杂因子

          更复杂的因子怎么办?

          前面这个因子 factorDoubleEMA 其实只涉及一只股票自身在时间序列上的价格信息,但诸如 Alpha#1、Alpha#98 这类涉及在截面上做横向排序和计算的因子,是无法将逻辑封装在一个自定义函数中,然后在一个 SQL select 语句中调用的。


          以 Alpha#1 因子的计算逻辑为例:



          Step 1

          判断每日回报率,如果小于0,则返回前20天回报率的标准差,否则返回收盘价;保留正负号平方处理;

          Step 2

          从过去5个值找出最大值,返回其对应索引,确定权重:过去5天最大收盘价索引作为权重,或过去5天最大前20天回报率标准差的索引作为权重。

          Step 3

          先横向排序,再进行中性化变换;一半为正一半为负,取正数股票为买入股票池。
          Alpha#1 因子的计算逻辑涉及到时间序列横截面两个维度的计算。这种情况下,我们建议将表作为自定义因子函数的入参,内部用 SQL 进行操作,函数最后返回一个表。

          同样基于前面的模拟原始数据,要计算4000只股票2020年2月份的分钟频 Alpha#1因子值,我们先用 select 语句生成如下数据表:

          将该表作为自定义函数 alphaSQL 的入参,在函数中分别进行时序处理和截面计算,这里函数中第一行对应 Step1 和 Step 2,第二行对应 Step 3 截面处理:
            def alpha1SQL(t){
                res = select tradetime, securityid, mimax(pow(iif(ratios(close) - 1 < 0, mstd(ratios(close) - 1, 20), close), 2.0), 5) as val from t context by securityid
                return select tradetime, securityid, rank(val, percent=true) - 0.5 as val from res context by tradetime
            }
            input_alpha1 = select tradetime,securityid,close from loadTable("dfs://k_minute_level","k_minute") where date(tradetime) between 2020.02.01 : 2020.02.28
            select top 10 * from input_alpha1
            alpha1DDBSql = alpha1SQL(input_alpha1)

            这样就能轻松得到需要的因子值,返回结果也是一个表:



            以 Alpha#98 因子的计算逻辑为例:



            Alpha#98 因子计算需要用到 vol、open、vwap 等多个字段的历史数据,并且同样涉及截面计算。我们可以以如下所示的数据表为入参,传入自定义计算因子的函数中:
              def alpha98SQL(mutable t){ //mutable:可变参数在函数体中可以被修改
              update t set adv5 = mavg(vol, 5), adv15 = mavg(vol, 15) context by securityid
              update t set rank_open = rank(X = open,percent=true), rank_adv15 =rank(X=adv15,percent=true) context by date(tradetime)
              update t set decay7 = mavg(mcorr(vwap, msum(adv5, 26), 5), 1..7), decay8 = mavg(mrank(9 - mimin(mcorr(rank_open, rank_adv15, 21), 9), true, 7), 1..8) context by securityid
              return select tradetime,securityid, `alpha98 as factorname, rank(X =decay7,percent=true)-rank(X =decay8,percent=true) as val from t context by date(tradetime)
              }
              input_alpha98 = select tradetime,securityid, vwap,vol,open from loadTable("dfs://k_minute_level","k_minute") where date(tradetime) between 2020.02.01 : 2020.02.28
              alpha98DDBSql = alpha98SQL(input_alpha98)
              返回结果:

              可以看到,面板模式与 SQL 模式为大家面对不同的计算场景提供了选择空间。学会灵活运用多种范式的编程,充分利用 DolphinDB 矩阵计算的高效能,结合强大的分布式并行计算框架,将能更轻松地面对各类金融复杂因子计算,开发不同风格的因子。

              总结一下——对于日频等低频数据,我们推荐大家使用面板模式,结合 DolphinDB 的一系列优化函数,实现既直观、又高效的因子计算。对于分钟频、快照、逐笔等中高频数据,我们推荐大家使用 SQL 模式,结合 DolphinDB 的分区存储方案,高效实现库内并行计算。





              未完待续



              分钟频、日频、快照、逐笔……金融市场的数据种类繁多,频率也很多样。不同频率数据的因子,有着不同的特点。高频因子计算如何保证高性能?快照多档数据存储起来太麻烦?不同类型的数据怎么实时聚合?在接下来的系列内容中,我们都将为大家逐一详解,全方面拆解和探索因子挖掘的最佳实践,敬请期待!
              扫码获取因子计算加速解决方案👇👇

              点击阅读原文获取因子计算加速解决方案

              文章转载自DolphinDB智臾科技,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论