2. 因子库架构设计:分层、模块化与可扩展性
因子库是量化投研体系的“原料车间”。一个设计良好的因子库架构,需要同时满足数据完整性、计算效率、存储可追溯和服务高可用四个维度的要求。本节从分层架构、模块化设计、可扩展性三个层面展开,并给出可落地的架构参考。
2.1 整体架构设计原则
在开始分层之前,需要确立以下四条核心原则,它们将贯穿整个架构设计过程:
| 原则 | 说明 | 落地检查点 |
|---|---|---|
| 数据与计算分离 | 原始行情数据、因子计算逻辑、因子结果存储三者解耦,互不依赖底层实现 | 更换数据源(如从Wind切换至聚宽)时,计算层代码无需修改 |
| 幂等性与可重算 | 同一因子、同一参数、同一时间区间,多次计算必须得到完全相同的结果 | 因子值表必须包含factor_id + date + asset_id唯一键 |
| 增量优先 | 日常运行只处理新增数据(如当日行情),全量重算仅作为异常恢复手段 | 计算任务支持start_date和end_date参数 |
| 可观测性 | 每个因子的计算耗时、数据质量、缺失率、分布漂移等指标必须可监控 | 因子元数据表记录每次计算的版本号和运行日志 |
架构决策提示: 初期团队规模较小时,建议优先保证“数据与计算分离”和“幂等性”,这两条是后续扩展和回测可信度的基础。可观测性可以在因子数量超过50个时再系统化建设。
2.2 分层架构:四层职责与交互
因子库采用经典的四层架构:数据层 → 计算层 → 存储层 → 服务层。每一层只与相邻层通过明确的接口通信,避免跨层依赖。
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 |
2.4 架构落地检查清单
实施建议: 在开始编码前,对照以下清单逐项确认架构设计是否完备。这可以避免后期大规模重构。
- 数据层: 是否所有数据源都实现了统一的
get_data(start, end, assets, fields)接口? - 计算层: 因子定义是否支持参数化?是否支持依赖其他因子?增量计算是否可配置?
- 存储层: 因子值表是否有唯一键约束?是否支持按时间分区?是否有版本号字段?
- 服务层: API是否支持批量查询?是否有限流和鉴权机制?
- 扩展性: 新增一个因子是否需要修改超过2个文件?是否需要重启服务?
- 可观测性: 每个因子的计算耗时、数据行数、缺失率是否被记录?
常见陷阱: 很多团队在初期将因子计算逻辑直接写在回测策略中,导致因子无法复用、回测结果不可复现。请务必在因子数量超过10个之前完成分层架构的搭建,否则后续迁移成本将指数级上升。