2. 因子库架构设计:分层、模块化与可扩展性

因子库是量化投研体系的“原料车间”。一个设计良好的因子库架构,需要同时满足数据完整性、计算效率、存储可追溯和服务高可用四个维度的要求。本节从分层架构、模块化设计、可扩展性三个层面展开,并给出可落地的架构参考。

2.1 整体架构设计原则

在开始分层之前,需要确立以下四条核心原则,它们将贯穿整个架构设计过程:

原则 说明 落地检查点
数据与计算分离 原始行情数据、因子计算逻辑、因子结果存储三者解耦,互不依赖底层实现 更换数据源(如从Wind切换至聚宽)时,计算层代码无需修改
幂等性与可重算 同一因子、同一参数、同一时间区间,多次计算必须得到完全相同的结果 因子值表必须包含factor_id + date + asset_id唯一键
增量优先 日常运行只处理新增数据(如当日行情),全量重算仅作为异常恢复手段 计算任务支持start_date和end_date参数
可观测性 每个因子的计算耗时、数据质量、缺失率、分布漂移等指标必须可监控 因子元数据表记录每次计算的版本号和运行日志
架构决策提示: 初期团队规模较小时,建议优先保证“数据与计算分离”和“幂等性”,这两条是后续扩展和回测可信度的基础。可观测性可以在因子数量超过50个时再系统化建设。

2.2 分层架构:四层职责与交互

因子库采用经典的四层架构:数据层 → 计算层 → 存储层 → 服务层。每一层只与相邻层通过明确的接口通信,避免跨层依赖。

因子库四层架构总览 服务层 (Service Layer) 因子查询API · 因子值订阅 · 因子画像展示 · 权限控制 存储层 (Storage Layer) 因子值宽表 · 因子元数据 · 原始数据缓存 · 版本快照 计算层 (Compute Layer) 因子表达式引擎 · 分布式计算调度 · 依赖管理 · 增量/全量切换 数据层 (Data Layer) 行情数据接入 · 财务数据清洗 · 另类数据标准化 · 数据质量校验

2.2.1 数据层(Data Layer)

负责对接各类原始数据源,完成清洗、对齐、去重和标准化。输出统一格式的宽表(每行一个时间-资产组合,每列一个原始字段)。

  • 数据源适配器: 每个数据源(Wind、聚宽、Bloomberg、自建数据库)实现统一的DataSource接口。
  • 质量校验器: 检查缺失率、异常值、复权一致性、停牌标记等,不合格数据进入告警队列。
  • 时间对齐器: 将不同频率的数据(日频、分钟频、财报季频)对齐到统一的交易日历。

2.2.2 计算层(Compute Layer)

因子计算的核心引擎。接收来自数据层的标准化宽表,按照因子定义执行计算逻辑。

  • 表达式引擎: 支持类SQL或Python表达式的因子定义,如close / ma(close, 20)。
  • 依赖解析器: 自动识别因子间的依赖关系(如因子A依赖因子B),确保计算顺序正确。
  • 调度器: 支持DAG(有向无环图)任务编排,可配置增量计算或全量重算。

2.2.3 存储层(Storage Layer)

存储因子计算结果和元数据。设计上强调读优化和版本可追溯。

  • 因子值宽表: 以(date, asset_id)为行索引,每列对应一个因子,适合向量化回测读取。
  • 因子元数据表: 记录因子名称、创建人、参数、计算版本、依赖列表、最后更新时间。
  • 快照管理: 每次全量重算生成一个快照版本,支持按版本回滚。

2.2.4 服务层(Service Layer)

对外暴露统一接口,屏蔽底层存储和计算细节。

  • RESTful API: 提供因子值查询、因子列表、因子详情等接口。
  • WebSocket 推送: 实时推送因子值更新(用于实盘信号)。
  • 权限与计量: 控制不同用户/策略对因子的访问范围,并记录调用次数。

2.3 模块化与可扩展性设计

模块化设计的核心目标是:新增一个因子时,不需要修改已有代码。这要求架构具备以下能力:

2.3.1 插件式因子注册

每个因子是一个独立的Python类(或配置文件),通过注册机制加入系统。系统启动时扫描指定目录,自动加载所有因子定义。

# 示例:因子注册基类
class BaseFactor:
    name: str               # 因子唯一名称
    params: dict            # 因子参数,如窗口期
    dependencies: list      # 依赖的其他因子名称列表
    data_requirements: list # 需要的原始数据字段

    def calculate(self, data: pd.DataFrame) -> pd.Series:
        raise NotImplementedError

# 注册示例:20日动量因子
@register_factor
class Momentum20(BaseFactor):
    name = "momentum_20d"
    params = {"window": 20}
    dependencies = []
    data_requirements = ["close"]

    def calculate(self, data):
        return data["close"].pct_change(self.params["window"])

2.3.2 配置驱动而非代码驱动

因子参数、计算频率、数据源映射等通过YAML或JSON配置文件管理,避免硬编码。

# factor_config.yaml 示例
factors:
  - name: momentum_20d
    enabled: true
    compute_frequency: daily
    params:
      window: 20
    data_source: wind_daily
    storage:
      table: factor_values
      partition: by_month

  - name: volatility_30d
    enabled: true
    compute_frequency: daily
    params:
      window: 30
    dependencies: ["momentum_20d"]  # 依赖其他因子

2.3.3 扩展点设计

在架构的关键位置预留扩展接口,方便后续接入新数据源、新计算引擎或新存储后端。

扩展点 接口/抽象类 典型扩展场景
数据源接入 DataSourceAdapter 新增加密货币交易所行情、另类数据(如卫星图像)
计算引擎 ComputeBackend 从单机Pandas切换至Dask分布式或Spark集群
存储后端 StorageBackend 从PostgreSQL切换至ClickHouse或DolphinDB
因子校验 FactorValidator 新增因子逻辑校验(如禁止未来函数、限制极值比例)
输出格式 OutputFormatter 新增Parquet格式输出、直接写入回测平台API
模块化扩展点:插件式架构 因子计算核心引擎 调度 · 依赖解析 · 表达式执行 数据源适配器 Wind · 聚宽 · 自定义 存储后端 PostgreSQL · ClickHouse 计算后端 Pandas · Dask · Spark 因子校验器 未来函数 · 极值 · 缺失 每个扩展点只需实现对应接口,即可在不修改核心引擎的前提下接入新能力

2.4 架构落地检查清单

实施建议: 在开始编码前,对照以下清单逐项确认架构设计是否完备。这可以避免后期大规模重构。
  1. 数据层: 是否所有数据源都实现了统一的get_data(start, end, assets, fields)接口?
  2. 计算层: 因子定义是否支持参数化?是否支持依赖其他因子?增量计算是否可配置?
  3. 存储层: 因子值表是否有唯一键约束?是否支持按时间分区?是否有版本号字段?
  4. 服务层: API是否支持批量查询?是否有限流和鉴权机制?
  5. 扩展性: 新增一个因子是否需要修改超过2个文件?是否需要重启服务?
  6. 可观测性: 每个因子的计算耗时、数据行数、缺失率是否被记录?
常见陷阱: 很多团队在初期将因子计算逻辑直接写在回测策略中,导致因子无法复用、回测结果不可复现。请务必在因子数量超过10个之前完成分层架构的搭建,否则后续迁移成本将指数级上升。