← 返回博客列表

【魔码量化工程实战进阶 #25】多因子工程化:去极值、标准化、中性化与等权合成

2026年08月31日 18:06 · 魔码数服 · 魔码量化工程实战进阶

【魔码量化工程实战进阶 #25】多因子工程化:去极值、标准化、中性化与等权合成

作者:魔码数服 | 系列:魔码量化工程实战进阶

一、为什么你的多因子策略一上线就崩

做量化的人大多踩过同一个坑:在网上扒几十个因子,回测曲线漂亮得离谱,一上模拟盘就大幅回撤;或者只顾着找"黑马因子",把原始数据直接相加选股,最后赚的钱全是蹭行业风口、赌小盘行情,根本没有真正的阿尔法。

问题往往不在因子本身,而在因子工程。一个经过精细化清洗的普通因子,实用性远超未经处理的"顶级因子"。绝大多数新手把 80% 的精力花在找因子上,只用 20% 随便处理数据——这完全本末倒置。

本文带你把多因子最关键的预处理链路跑通:去极值 → 标准化 → 中性化 → 等权合成。全程使用魔码量化行情 API 的纯 HTTP 接口拉取真实数据,零 SDK 依赖,复制即可运行。

二、本文你将得到什么

  • 一段可直接运行的 Python 代码,用 requests 纯 HTTP GET 拉取魔码日线数据;
  • 单股时间序列因子(动量 / 波动率 / 流动性)的真实计算示例;
  • 截面"三板斧"(去极值、标准化、中性化)的完整实现与真实输出;
  • 一个被很多人忽略的真相:中性化之后,有些"高动量"股票会原形毕露
  • 工程化落地的几个避坑点(频率限制、等权基线、缺失行业/市值接口时的务实中性化)。

三、数据从哪来:魔码 HTTP 接口

魔码数服提供纯 HTTP 行情接口,无需安装任何 SDK,一个 requests.get 就能拿到数据。下面这段代码拉取单只股票的日线历史:

import requests

BASE = "https://api.momaapi.com"
# 演示证书,请换成你自己的正式证书:https://www.momaapi.com/certificate-apply.html
LIC = "TEST-API-TOKEN-MOMA-836089C22111"

def fetch_history(code):
    url = f"{BASE}/hsstock/history/{code}/d/n/{LIC}?st=20250101&et=20260830"
    bars = requests.get(url, timeout=20).json()
    rows = [{
        "date": b["t"],
        "close": float(b["c"]),
        "vol": float(b["v"]),
        "amount": float(b["a"]),   # 历史 K 线成交额字段是 a
    } for b in bars]
    return rows

data = fetch_history("600519")
print(data[0])   # 单根 K 线示例

接口返回的是标准 JSON 数组,每根日线包含 t(日期)、o/h/l/c(开高低收)、v(成交量)、a(成交额)等字段。下面是该接口对 600519 真实返回的一根日线样例:

{"a": 980347616, "c": 11.65, "h": 11.72, "l": 11.63, "o": 11.7, "pc": 11.72, "sf": 0, "t": "2025-09-15", "v": 840387}

注意:魔码套餐带"次/分钟"频率限制,批量拉取多只股票时请在循环里加一点点间隔(如 time.sleep(0.15)),避免瞬时并发触发限流。

四、第一步:单股时间序列因子

拿到日线后,先在单只股票的时间序列上算出基础因子。这里演示三个最常用、且完全来自量价数据的因子:

  • 20 日动量 mom20 = 收盘价 20 日收益率(正向,越高越看好);
  • 20 日波动率 vol20 = 日收益率滚动 20 日标准差(反向,越高越规避);
  • 20 日流动性 liq20 = 成交额 20 日均值(正向,流动性越好越稳健)。

下面是 600519 真实数据算出的近 5 个交易日因子(动量、波动率已换算为百分比,流动性单位亿元):

date close mom20(%) vol20(%) liq20(亿)
2025-11-25 11.80 2.88 0.87 12.23
2025-11-26 11.69 2.63 0.88 12.16
2025-11-27 11.71 2.90 0.88 12.21
2025-11-28 11.61 2.56 0.90 12.17
2025-12-01 11.69 2.27 0.89 12.23

可以看到,量价数据天然是厚尾、偏态的,不能直接相加——这正是下一步"截面三板斧"存在的意义。

五、第二步:截面三板斧(去极值 / 标准化 / 中性化)

多因子最关键的不是选多少因子,而是同一交易日、全市场截面上把每个因子处理干净。下面是一段可直接运行的截面处理代码:

import numpy as np
import pandas as pd

def mad_winsorize(s, k=5.0):
    """绝对中位差法去极值,抗厚尾"""
    med = s.median()
    mad = (s - med).abs().median()
    if mad == 0:
        return s
    lo, hi = med - k * 1.4826 * mad, med + k * 1.4826 * mad
    return s.clip(lo, hi)

def zscore(s):
    return (s - s.mean()) / (s.std(ddof=0) + 1e-12)

def neutralize_market(signal, mkt_ret):
    """对市场收益回归取残差,剥离系统性 Beta 暴露"""
    y = signal - signal.mean()
    x = mkt_ret - mkt_ret.mean()
    if x.std() < 1e-12:
        return y
    beta = np.cov(y, x)[0, 1] / (x.std(ddof=0) ** 2 + 1e-12)
    return y - beta * x

# g 为某交易日的截面 DataFrame,含 mom20 / vol20 / liq20 / ret 列
for f in ["mom20", "vol20", "liq20"]:
    z = zscore(mad_winsorize(g[f]))
    g[f + "_z"] = zscore(z)              # 去极值后再标准化
g["mom20_n"] = neutralize_market(g["mom20_z"].values, g["ret"].values)  # 中性化
g["score"] = g["mom20_n"] + g["liq20_z"] - g["vol20_z"]                # 等权合成

三板斧逐一解释:

  1. 去极值:金融市场数据天生厚尾,用固定分位数截断(MAD 法)比 3σ 更稳,不会误删合理尾部标的,也不会漏掉极端异常值。
  2. 标准化(z-score):净利润单位是亿、换手率是百分比,量纲天差地别,直接相加会被大数值垄断。标准化后所有因子处于同一尺度,等权才有数学意义。
  3. 中性化:这是最关键的一步。它回答一个问题——这只股票因子值高,是因为它本身好,还是恰好赶上了市场 Beta?回归取残差后,留下的是"同一市场环境下它的相对强弱",也就是更纯净的阿尔法。

说明:演示证书返回的是每只股票形态一致的固定样本(已知 artifact),截面因子标准差为 0,无法体现排名差异。因此下方用一段"结构等同正式证书拉取的全市场截面"的示意面板,运行上面同一套真实代码,仅用于演示算法输出。换成你的正式证书拉取真实全市场数据后,这段代码即产出你的真实排名。

示意截面处理后的因子与合成打分如下(已按综合得分降序):

code mom20_z vol20_z liq20_z mom20_n score
600519 1.519 -0.000 1.914 -0.120 1.794
601318 -0.169 -0.577 1.137 -0.016 1.698
600036 1.203 -0.866 0.359 -0.238 0.987
600887 -0.485 -1.443 -0.658 0.066 0.851
600276 0.148 -1.155 -0.359 -0.098 0.698
600030 -1.646 1.155 0.778 0.166 -0.211
002415 -1.224 0.577 0.120 0.124 -0.334
000333 -0.802 -0.289 -1.017 0.147 -0.581
000001 0.886 0.866 -0.897 -0.023 -1.787
000002 0.570 1.732 -1.376 -0.008 -3.116

六、中性化的意义:有些"高动量"是假象

把"原始动量排名"和"中性化合成后排名"放在一起对比,真相就出来了:

code mom20_z 原始动量排名 score 中性化后排名
600519 1.519 1 1.794 1
600036 1.203 2 0.987 3
000001 0.886 3 -1.787 9
000002 0.570 4 -3.116 10
600276 0.148 5 0.698 5
601318 -0.169 6 1.698 2
600887 -0.485 7 0.851 4
000333 -0.801 8 -0.581 8
002415 -1.224 9 -0.334 7
600030 -1.646 10 -0.211 6

注意 000001000002:它们的原始动量排名分别是第 3、第 4,看着很强;但中性化合成后暴跌到第 9、第 10。原因很简单——它们的动量几乎完全由市场 Beta 解释,剥离 Beta 之后所剩无几。如果你不做中性化,就会误以为抓住了阿尔法,实则只是在裸多市场。这正是中性化存在的价值。

七、工程化落地的几个避坑点

  1. 频率限制:魔码套餐带"次/分钟"上限,批量拉取多只股票务必加请求间隔,别把限流当接口故障。
  2. 等权优先于加权:没有足够长的样本外数据证明某个因子更可靠之前,先用等权跑通基线。用历史 ICIR 加权很容易过拟合——回测区间内 ICIR 高的因子,实盘未必继续高。
  3. 行业 / 市值中性化的务实替代:标准做法是回归行业哑变量 + 对数市值。若你当前接口未提供行业、市值字段,可先用本文的"对市场收益回归取残差"作为第一层中性化(剥离 Beta),待接入更丰富基础数据后再升级到行业 + 市值中性化。
  4. 先建基线,再迭代:因子选择、去极值分位数、合成方式里到处是主观选择。每次只改一个变量,并用样本外验证确认超额来自能力而非运气。

八、下篇预告

因子处理完、合成出打分,下一步就是验证它到底有没有预测能力。下一篇【魔码量化工程实战进阶 #26】横截面 IC 与分层回测,我会带你用魔码行情数据计算 Spearman IC、滚动 IC,并做五分位分层回测,看看你的因子是"真有效"还是"看着有效"。

免责声明

本文所有代码示例与数据均用于技术方法演示,不构成任何投资建议。市场有风险,投资需谨慎。文中涉及的因子、回测结论不预示未来收益,据此操作风险自担。


想用稳定、即取即用的行情数据搭建自己的多因子 pipeline?魔码数服提供纯 HTTP 行情接口,免 SDK、免运维,覆盖日线 / 分钟级(Pro 包)等多周期数据。免费证书即可开始:https://www.momaapi.com/certificate-apply.html

想亲自试一下?免费获取证书
客服微信
客服微信二维码