产品特色:质量控制与开源



  • 目录

    1. 概述
    2. 计算记过的准确性是产品的核心
    3. 数据质量标签是准确性的保证
    4. 如何构建数据质量标签
    5. 扩展性强

    1. 概述

    Sigma这款产品表现在外的是一些刻画客户投资行为的量化指标。但在这些展示指标的背后,该产品内部有一系列的机制去保证整个系统的稳定性和灵活性,其中最具特色的机制有如下两个:

    • 数据质量标签:对于每一个客户,每一个量化指标,都有一个相应的数据质量标签,它将表示该客户的量化指标计算结果是否准确。
    • 可扩展性强,极易进行二次开发:对于数据开发人员,整个系统是一个“白盒”,可以在现有量化指标的基础上快速开发出高质量的其他衍生指标。

    正所谓“外行看热闹,内行看门道”,这篇文章将简单介绍一下这两个特色机制,并讨论它们在整个产品里的作用和重要性。

    2. 计算结果的准确性是产品的核心

    Sigma这款产品将使用量化的方法,分析客户在A股市场的投资行为,并把分析结果反馈给客户以及投资顾问,帮助他们更好地进行投资和财富管理。也就是说,Sigma其实是一款提供数据服务的产品,但如果里面的量化指标计算不准确,会引起如下的不良后果

    • 产品无人问津,错误的指标无法为客户提供帮助,当然后续的产品推荐和财富管理也就是无法达到预期的效果。
    • 引起客户投诉,Sigma中有大量涉及客户收益的量化指标,比较敏感,错误的计算结果很容易引起客户的不满和投诉。

    既然计算结果的准确性就是这个产品的核心和存在意义,那么现状如何呢?以最简单的收益和收益率为例,虽然这两个量化指标听起来并不复杂,但由于证券行业所涉及业务的复杂性,导致得到准确的分析结果并不容易。比如下图是国内某大型券商提供的类似产品截图:

    0_1538040989463_产品特色错误图1.png

    为了有效地控制计算结果的质量,进而避免类似上面图片所展示的错误,产品设计上引入了数据质量标签来控制计算结果的质量,这也是Sigma这款产品最大的特色。

    当然我们认为,对于一款数据产品,如果没有提供这种类似的质量控制标签,那么产品基本上属于不可用的状态,就像开发好但没有经过任何测试的APP一样。

    3. 数据质量标签是准确性的保证

    3.1 为什么出错?

    在讨论数据质量标签的重要性之前,先简单阐述一下在证券行业,为什么收益和收益率的计算很难达到准确:

    • 算法层面
      • 收益和收益率的计算涉及很多复杂的财会处理,比如客户资产负债表重建、股票估值、交易现金流定义等。而且这些财会处理大多没有现成的公式、算法可以直接使用,需要根据不同的情况和业务定义处理方法。

        比如对于股票质押业务,客户可以将自己持有的价值100元的股票抵押给券商,而获得50元现金。这种业务从交易流水上来看,好像是客户用50元“卖掉”了自己价值100元的股票,而这就会引起收益计算的异常。为了准确地计算客户的收益,需要将抵押的100元股票加入客户的资产表,同时增加一笔50元的现金负债。

      • 券商业务的变化速度很快,这要求算法要能及时匹配上业务的变动。比如股票质押这一需要特殊处理的业务在最近两年被大规模地推广到“散户市场”,从而引起很多客户的收益、收益率计算异常。

    • 数据层面
      • 从业务流程上来看,数据中心或者大数据处在整个流程的末端,在它拿到客户交易数据之前,需要经过清算、结算等环节。这些环节的变动和误差都会引起最终计算结果的偏差。
      • 另外,原始交易数据的产生和指标的计算通常是在两个不同的系统下完成的。因此,原始数据的更新以及系统之间的同步常常会引起指标计算时的数据缺失,导致计算结果不准确。

    因此,在现有的架构里面,收益和收益率的计算几乎不可能做到100%准确。当然,我们也不能因噎废食,因为极少的很难避免的误差就完全不给客户提供这样有价值的数据服务。那么,合理而且有效的做法就是给每个量化指标打上数据质量标签,来表示这一项的计算是否准确。

    3.2 为什么数据质量标签这么重要?

    数据质量标签在产品的各个方面都发挥着很重要的作用,这个标签可以衡量和提供产品的质量,进而作为整个产品验收的基础;它还可以作为运营和运维指标,监控整个系统的运行情况,比如当天是否出现问题,受问题影响的客户规模有多大;另外对于数据治理,这个指标还可以帮助我们快速地定位问题和解决问题。下面将就这些点展开讨论。

    • 从产品质量的角度来讲,如果没有这个标签,那么整个产品的质量

      • 要么依赖于第三方供应商声誉和口头承诺
      • 要么依赖于券商内部数据开发人员的个人经验,比如说他对证券行业的整个业务流程都相当了解,对每一个交易品种都知道如何定义算法来处理,然后他开发的代码又经过测试,没有或者很少bug

      也就是说,并没有一个量化的指标来衡量整个产品的数据质量。

    • 从产品验收的角度来讲,数据产品和其他的软件产品不同,它并没有一个明确的验收标准。事实上,数据处理的代码相对来说是很容易“跑通”的,即使有明显逻辑缺陷的代码也能计算出收益和收益率这两个指标(比如上面图中的错误)。而且由于交易数据都由交易系统生成,所以很难在系统里模拟出软件质量控制中常用的测试用例和测试数据来调试和验收(特别是对于股票交易,测试用例需要很长周期之内的多种数据联合模拟,非常难构建)。因此,传统的软件测试很难给数据产品的开发提供帮助。而有了数据质量标签,那么就可以为基础去验收产品的质量,使得券商在整个产品层面占据主动地位。

    • 从产品运营、运维的角度来讲,这个标签也是一个极其重要的工具和参考指标

      • 从运营的角度来讲。在客户之前发现错误,并提前制定好对应的对策(比如对于计算结果有误差的客户隐藏结果或者提前说明)可以极大地提升产品体验,从而有效地降低客户投诉。否则,即使是很明显的错误,券商也只能后于客户发现,在处理投诉时会相当的被动。
      • 从运维的角度来讲。与其他应用不同的是,数据产品的运维分为两个部分,一是工程层面上的,这与其他应用是类似的,主要监控接口是否可用,机器的压力如何等。二是数据层面上的运维,也就是对外提供的数据有多少是正确的,是否发生了数据层面上的异常,比如数据缺失,计算错误等。这个也是非常重要的,因为即使应用是可用的,但数据错了,数据产品也相当于宕机的状态。这也是为什么我们在前文中声称对于一款数据产品,如果没有提供这种类似的标签,那么产品基本上属于不可用的状态。
    • 从数据治理的角度来讲,质量控制标签还可以帮助我们定位问题,发现到底是数据流程中的哪个环节出了错误,进而可以联合其他部门提高券商内部数据的质量可用性。

    4. 如何构建数据质量标签

    既然质量控制标签如此重要,那么问题来了,我们应该如何在没有“标准答案”的条件下去构建这个标签呢?

    Sigma采用的方法是,通过五类核对去一步步生成数据质量标签,也就是说未能通过这五类核对的计算结果会被标记为数据质量有问题。这和其他软件开发的测试有些类似,通过丰富的测试案例和场景模拟去提升软件的质量。这五类核对分别为:

    • 会计准则校正
    • 不同算法交叉复核
    • 公开数据校验
    • 量化指标异常检测
    • 模拟交易数据验证

    经过这五类的核对规则,我们就生成了较完备而且可用的数据质量标签。

    4.1 会计准则校正

    每一位在A股市场上的投资者都可以被视为一个小的基金公司,那么从公司会计的角度来讲,每一个投资者都对应着三张表:

    • 资产负债表,这对应着投资者每天在A股市场上的持仓、负债等数据
    • 现金流表,这对应着投资者每天在A股市场上的交易、资金转移等操作记录
    • 利润表,这对应着我们要计算的收益、收益率等基础量化指标

    那么借鉴已有的会计准则,可以得到一系列硬性的检测规则,比如持仓量核对,也就是说:T-1日的资产负债持有量 + T日的交易量 = T日的资产负债持有量,相应的还有现金持有量核对。

    虽然这条规则并不直接影响收益和收益率的计算,但对于没有通过这些规则的数据,计算结果一定有问题,所以相应的数据质量标签会标记该数据有问题。

    4.2 不同算法交叉比对

    对于同一个量化指标,通常有多种不同的算法。这些算法虽然计算的指标是相同的,但是使用的数据和具体的算法逻辑是不同的,比如计算一个客户在T日的股票收益,可以有两种不同的算法:

    • 从细分到整体的方法。也就是说,首先计算该客户T日在A股市场上每笔交易的收益,然后将每笔交易的收益进行加总,得到这个客户在A股市场上的总收益。
    • 直接从整体开始的方法。也就是说,计算客户T-1日股票持仓的市值、T日股票持仓的市值以及T日A股市场的资产流入流出,在这三者的基础上,计算出客户在T日的股票收益。

    在计算准确的情况下,上面两种算法得到的同一量化指标应该是一样的。当两者不同时,相应的数据会被打上不合格的标签。

    通俗点讲,就是对于一个指标,正着反着都计算一次,然后进行对比,找出其中可能出错的计算结果。从理论上来讲,这其实是借鉴了公司财务会计中的财务数据交叉复核方法(财务数据之间存在勾稽关系,可以通过交叉复核进行验证)。

    4.3 公开数据校验

    A股市场上的公开数据校验将利用公开数据衍生得到的量化指标来辅助检验计算结果的正确与否。这部分规则可以大致分为两类:

    • 交易业务核对,也就是说客户数据里记录的交易行为是否与公开数据一致,比如客户持仓的股票是否在股票除权除息日(有且仅有在除权除息日)得到了相应的分红和分股。
    • 波动范围核对,比如客户在A股市场上每笔交易的每日收益率是否超过该股票在当日的波动幅度。

    同样地,没有通过这些规则的量化指标都是有数据质量问题的,相应的标签会标明该数据有问题。

    4.4 量化指标异常检测

    由于量化指标都是每日增量进行更新的,因此在一段时间内,任意一个量化指标都形成了一个特定的时间序列。比如近六个月内的每日收益率就形成了一个时间序列(比如下图)。对于这样的时间序列,我们利用人工智能里面的异常检测技术对其进行检测,从中标记出异常的计算结果,比如下图中用箭头标记出来的数据点。

    0_1538041010675_产品特色错误图2.png

    这就好像我们模拟了一个数据监测员,每天先于客户去看得到的计算结果,并将那些可能的、明显错误的数据标注出来,反馈给产品的运营人员和开发人员。

    4.5 模拟交易数据验证

    Sigma这款产品有专门的模拟模块,它负责产生正常的交易流水以及故意引入的数据错误。这样一来,对于这些模拟的数据,我们事先知道哪些是正确的,哪些错误的。这些模拟数据可以保证数据质量标签本身的计算是没有问题的,同时也可以帮助我们去监测整个系统的运行情况。

    5. 扩展性强

    对客户投资行为进行量化分析是一个长期的需求,另外量化分析得到的指标会被大量应用于用户画像、资讯推荐、财富管理等领域。因此,会不时需要计算新的量化指标。这就要求整个量化分析系统在数据层面的可扩展性很强,在新需求到来时,能够快速地基于已有的结果得到想要的结果,而不用每次都需要从最原始的交易数据开始去构建整个指标体系。除了能节省时间以外,在现有的基础上,进行二次开发,还能尽可能地保证计算结果的正确性,因为Sigma系统里的基础指标都是经过反复校验的。

    扩展性强是Sigma这款产品另一大特征,数据开发人员能够非常容易地在现有系统的基础上进行二次开发。为了做到这一点,在数据层面上,Sigma借鉴公司会计的方法重构了有关客户投资行为的三张表,而会计理论保证了这三张表可以衍生出其他各式各样的量化指标;在系统架构上,Sigma通过代码开源和模块划分来保证整个系统的可扩展性。

    5.1 代码开源和模块划分

    Sigma整个系统是代码开源,从最初的数据清洗、指标计算到最后面的前端展示,所有的源代码都开放,方便数据工程师在系统已有指标的基础上进行二次开发。

    另外,Sigma在功能上是按模块进行划分的,而且是从最细颗粒度的交易开始构建整个指标体系。具体来说,Sigma从计算难道最大的每笔交易收益开始,逐步计算刻画客户投资行为的量化指标。有了这些最基础的指标,后面的计算就变得相对简单而且独立(比如后文所举的例子),可以让数据开发人员聚焦于上层的指标开发,而不用受困于繁琐的数据清洗和业务梳理。

    下面以一个简单的例子来展示如何在Sigma系统的基础上进行快速的二次开发。

    5.2 二次开发示例

    由于业务需要,需要统计一段时间内(2017-04-01至2017-06-01),

    • 客户的交易总次数、盈利次数、亏损次数、平均盈利额/率、平均亏损额/率
    • 客户在持仓周期为短、中、长三种情况下的盈利情况,定义持仓小于5天为短期,持仓大于等于5天且小于20天为中期,持仓大于20天为长期

    对于这个需求,在Sigma中,有如下的表记录做多情况下每笔交易的收益情况FDATA.stock_close_o_trade_long_data(完整的模块划分和表结构请见这个链接):

    字段名 字段解释
    trade_id 客户编号
    secu_acc_id 资金账号
    prd_no 股票代码
    return 收益
    return_rate 收益率
    weighted_term 加权持仓周期
    busi_date 业务日期

    那么用如下的HIVE语句就可以得到计算结果:

    • 对于第一个需求,具体的实现如下
    SELECT
    trade_id,
    count(*) as trade_num,
    sum(case when return >= 0 then 1 else 0 end) as gain_num,
    sum(case when return < 0 then 1 else 0 end) as loss_num,
    avg(case when return >= 0 then return else null end) as avg_gain,
    avg(case when return < 0 then return else null end) as avg_loss,
    avg(case when return_rate >= 0 then return_rate else null end) as avg_gain_rate,
    avg(case when return_rate < 0 then return_rate else null end) as avg_loss_rate
    FROM FDATA.stock_close_o_trade_long_data
    WHERE busi_date >= '2017-04-01' and busi_date <= '2017-06-01'
    group by trade_id
    
    • 对于第二个需求,具体的实现如下:
    SELECT
    trade_id,
    holding_term,
    avg(case when return >= 0 then return else null end) as avg_gain,
    avg(case when return < 0 then return else null end) as avg_loss,
    avg(case when return_rate >= 0 then return_rate else null end) as avg_gain_rate,
    avg(case when return_rate < 0 then return_rate else null end) as avg_loss_rate
    FROM
    (SELECT trade_id, return, return_rate,
    case when weighted_term < 5 then "short_term"
    when weighted_term < 20 and weighted_term >= 5 then "middle_term"
    when weighted_term >= 20 then "long_term" end as holding_term 
    FROM FDATA.stock_close_o_trade_long_data
    WHERE busi_date >= '2017-04-01' and busi_date <= '2017-06-01') a
    group by trade_id, holding_term