为什么大多数量化项目在面试中毫无价值
打开任何一个"量化求职者"的 GitHub,你会看到相似的内容:一个用 Backtrader 或 Zipline 跑的均线策略回测,Sharpe Ratio 写着 1.8,最大回撤 12%,然后就没了。
面试官看到这类项目的反应通常是:"这个策略在样本外数据上表现如何?你如何处理 look-ahead bias?交易成本是怎么建模的?这个 Sharpe 是日频还是年化?"
如果你无法回答这些问题,这个项目不仅没有加分,反而暴露了你对量化研究的理解停留在表面。本文的目标是教你做一个能经受面试官追问的量化项目——展示你真正理解数据、统计和市场微结构的作品。
第一步:选题——什么样的项目值得做
原则一:选择你能讲清楚每一行代码的项目
不要因为某个策略"听起来很高级"就选它。一个简单但理解深刻的项目,远胜于一个复杂但一知半解的项目。
原则二:项目要有"研究问题",而不只是"实现"
差的项目描述:"用移动平均线策略回测了 A 股数据" 好的项目描述:"研究了 A 股市场中动量效应的持续性——在控制市值和流动性因子后,过去 12 个月的动量信号是否在未来 1 个月仍然有效?"
前者是实现,后者是研究。量化公司招的是研究员,不是码农。
推荐选题(按难度分级)
| 难度 | 项目方向 | 核心研究问题 | 适合岗位 | |------|---------|------------|--------| | ⭐⭐ | 单因子有效性检验 | 某因子在特定市场的 IC/IR 是否显著? | QR 初级 | | ⭐⭐⭐ | 统计套利对子交易 | 协整关系是否稳定?如何动态估计对冲比率? | QT/QR | | ⭐⭐⭐ | 订单簿微结构分析 | 买卖价差与流动性的日内规律是什么? | QT/做市商 | | ⭐⭐⭐⭐ | 多因子选股模型 | 如何构建正交化因子?如何处理因子衰减? | QR 高级 | | ⭐⭐⭐⭐ | 期权隐含波动率曲面 | IV Smile 的时间序列特征是什么? | QR/衍生品 |
第二步:数据清洗——真正的工作在这里
大多数教程跳过了数据清洗,直接用"干净的"示例数据。但在真实量化研究中,数据清洗占据 60-70% 的时间,而且是展示专业度的关键环节。
以 A 股日频数据为例,必须处理的三个核心问题:
1. 幸存者偏差(Survivorship Bias)
这是最常见也最致命的错误。如果你只用当前还在交易的股票做回测,你的结果会严重高估策略表现,因为你隐性地排除了所有退市的"失败者"。
正确做法:使用历史时点的股票池,在每个回测日期只包含当时已上市且未退市的股票。
2. 前视偏差(Look-ahead Bias)
错误做法:用当天收盘价计算信号,然后用当天收盘价成交。正确做法:信号用前一天的数据,成交用次日开盘价。
3. 股票拆分与复权处理
使用后复权价格(backward adjusted)进行回测。注意:前复权价格会随时间变化,不适合历史回测。
数据质量检查清单
| 检查项 | 常见问题 | 处理方法 | |--------|---------|--------| | 缺失值 | 停牌日期数据缺失 | 用前一日数据填充或标记为停牌 | | 异常值 | 数据录入错误(如价格为0) | 设置合理范围过滤 | | 幸存者偏差 | 只包含当前股票 | 使用历史时点股票池 | | 前视偏差 | 信号与收益日期对齐错误 | 严格使用 shift(1) 对齐 | | 复权处理 | 使用前复权价格 | 改用后复权价格 |
以下是数据清洗的核心代码框架,展示如何正确处理前视偏差和幸存者偏差:
1import pandas as pd
2import numpy as np
3import akshare as ak
4
5# ─── Step 1: 获取历史股票池(含退市股票,避免幸存者偏差)─────────────────
6def get_universe_at_date(date: str, data_dir: str = './data/') -> list[str]:
7 """
8 返回指定日期的股票池(包含当时已上市且未退市的所有股票)
9 """
10 listing_df = pd.read_csv(f'{data_dir}stock_listing_history.csv', parse_dates=['list_date', 'delist_date'])
11 target_date = pd.Timestamp(date)
12 universe = listing_df[
13 (listing_df['list_date'] <= target_date) &
14 ((listing_df['delist_date'].isna()) | (listing_df['delist_date'] > target_date))
15 ]['stock_code'].tolist()
16 return universe
17
18# ─── Step 2: 正确对齐信号与收益(避免前视偏差)──────────────────────────
19def compute_forward_returns(prices: pd.DataFrame, holding_period: int = 5) -> pd.DataFrame:
20 """
21 计算未来 N 日收益率(信号日 T 的收益 = T+1 开盘到 T+N+1 开盘的涨跌幅)
22 使用 shift(-holding_period) 确保不存在前视偏差
23 """
24 # 使用开盘价成交,避免收盘价前视偏差
25 open_prices = prices['open']
26 # T 日信号 → T+1 开盘买入 → T+1+N 开盘卖出
27 fwd_returns = open_prices.shift(-1 - holding_period) / open_prices.shift(-1) - 1
28 return fwd_returns
29
30# ─── Step 3: 数据质量检查 ──────────────────────────────────────────────────
31def data_quality_check(df: pd.DataFrame) -> dict:
32 report = {}
33 # 缺失值比例
34 report['missing_rate'] = df.isnull().mean().to_dict()
35 # 异常值(价格为 0 或负数)
36 if 'close' in df.columns:
37 report['zero_price_count'] = (df['close'] <= 0).sum()
38 # 检查收益率极端值(单日涨跌幅超过 ±20% 视为可疑)
39 if 'close' in df.columns:
40 daily_ret = df['close'].pct_change()
41 report['extreme_returns'] = (daily_ret.abs() > 0.21).sum()
42 return report
43
44# 使用示例
45if __name__ == '__main__':
46 universe = get_universe_at_date('2023-01-01')
47 print(f'Universe size on 2023-01-01: {len(universe)} stocks')第三步:单因子检验的完整研究框架
以"换手率反转因子"为例,展示一个完整的、经得起追问的研究项目。
研究假设:过去 20 个交易日换手率异常高的股票,在未来 5 个交易日会出现反转(超额收益为负)。
为什么选这个因子? 因为它有经济学直觉(过度交易导致价格偏离基本面),有学术文献支撑,同时在中国市场有其特殊性(散户比例高、信息不对称)。
核心评估指标:IC 分析
IC(Information Coefficient)是衡量因子预测能力的核心指标,定义为因子值与未来收益的 Spearman 相关系数。
| 指标 | 含义 | 参考标准 | |------|------|--------| | IC 均值 | 因子平均预测能力 | |IC| > 0.03 有参考价值,> 0.05 较强 | | IR (IC/ICSD) | 因子稳定性 | > 0.5 为可用,> 1.0 为优秀 | | IC > 0 比例 | 因子方向一致性 | > 55% 较好 |
处理交易成本(大多数项目忽略的关键)
单次交易成本(买入+卖出)= 佣金率×2 + 印花税 + 滑点×2 ≈ 万三×2 + 千一 + 0.2%×2 = 0.66%。每期成本 = 换手率 × 单次成本。扣除交易成本后,很多看起来"盈利"的策略实际上是亏损的——这正是展示你专业度的地方。
以下是完整的 IC 分析代码,这是面试中最常被追问的部分:
1import pandas as pd
2import numpy as np
3from scipy import stats
4import matplotlib.pyplot as plt
5
6# ─── 换手率反转因子计算 ──────────────────────────────────────────────────────
7def compute_turnover_reversal_factor(
8 turnover: pd.DataFrame, # 日频换手率,index=date, columns=stock_code
9 lookback: int = 20 # 过去 N 日均换手率
10) -> pd.DataFrame:
11 """
12 计算换手率反转因子:过去 lookback 日均换手率(越高分越高)
13 因子方向:做空高换手率股票(预期反转)
14 """
15 # 截面标准化:每个截面日期内进行 z-score 标准化
16 factor = turnover.rolling(lookback).mean()
17 # 截面 z-score(消除时序趋势)
18 factor_zscore = factor.apply(
19 lambda row: (row - row.mean()) / (row.std() + 1e-8), axis=1
20 )
21 return factor_zscore
22
23# ─── IC 分析 ────────────────────────────────────────────────────────────────
24def compute_ic_series(
25 factor: pd.DataFrame,
26 forward_returns: pd.DataFrame
27) -> pd.Series:
28 """
29 计算每个截面日期的 IC(Spearman 相关系数)
30 IC_t = Spearman(factor_{t}, return_{t+1:t+N})
31 """
32 ic_series = {}
33 for date in factor.index:
34 if date not in forward_returns.index:
35 continue
36 f = factor.loc[date].dropna()
37 r = forward_returns.loc[date].dropna()
38 common = f.index.intersection(r.index)
39 if len(common) < 30: # 至少需要 30 只股票
40 continue
41 ic, _ = stats.spearmanr(f[common], r[common])
42 ic_series[date] = ic
43 return pd.Series(ic_series)
44
45# ─── 汇总报告 ────────────────────────────────────────────────────────────────
46def ic_summary_report(ic_series: pd.Series) -> dict:
47 return {
48 'IC Mean': round(ic_series.mean(), 4),
49 'IC Std': round(ic_series.std(), 4),
50 'IR (IC/ICSD)': round(ic_series.mean() / ic_series.std(), 4),
51 'IC > 0 Ratio': round((ic_series > 0).mean(), 4),
52 't-statistic': round(stats.ttest_1samp(ic_series.dropna(), 0).statistic, 4),
53 'p-value': round(stats.ttest_1samp(ic_series.dropna(), 0).pvalue, 6),
54 }
55
56# 使用示例
57if __name__ == '__main__':
58 # 假设 turnover_df 和 fwd_returns_df 已经准备好
59 factor = compute_turnover_reversal_factor(turnover_df, lookback=20)
60 ic = compute_ic_series(factor, fwd_returns_df)
61 report = ic_summary_report(ic)
62 print(pd.Series(report).to_string())
63 # 输出示例:
64 # IC Mean -0.0312 ← 负值说明高换手率 → 未来负收益(符合反转假设)
65 # IR (IC/ICSD) -0.7841 ← 绝对值 > 0.5,因子可用
66 # IC > 0 Ratio 0.3210 ← 约 32% 的时间因子方向反转,需关注
67 # p-value 0.0023 ← 统计显著第四步:如何在面试中讲清楚这个项目
做完项目只是第一步,更重要的是能在 5 分钟内向面试官清晰地介绍。推荐使用量化版 STAR 框架:
S(Setup):我研究的问题是……,选择这个问题是因为…… M(Methodology):我使用了……方法,关键设计决策是……,我选择这个方法而不是……是因为…… R(Results):主要发现是……,IC 均值为……,IR 为……,在扣除交易成本后…… L(Limitations):这个研究的局限性在于……,如果有更多时间,我会……
面试官最常追问的问题:
"你如何处理幸存者偏差?" → 我使用了历史时点的股票池,包含了所有历史退市股票的数据。
"这个策略在样本外数据上表现如何?" → 我将 2015-2020 年作为样本内数据,2021-2024 年作为样本外验证。样本外 IR 从 0.8 下降到 0.5,有一定衰减,但仍然显著。
GitHub 仓库的专业呈现
一个专业的量化项目仓库应包含:清晰的 README(研究问题、核心发现、方法论、数据来源、已知局限性)、分层的 Jupyter Notebooks(数据清洗 → 因子构建 → IC分析 → 回测)、模块化的 Python 代码(data_loader.py、factor.py、backtest.py)以及单元测试(展示工程规范)。
AT&T Career 的项目辅导
AT&T Career 的 Quant Track 提供一对一的项目辅导,导师均为顶级量化机构的现任研究员,帮助学员从选题、数据处理到最终呈现,打造真正能在面试中说话的量化项目作品集。
常见问题 · FAQ
没有彭博或 Wind 数据,用什么免费数据源?+
AkShare(A股)、yfinance(美股)、Tushare(A股,需积分)是常用的免费数据源。对于求职项目,数据质量比数据量更重要——用一个市场的高质量数据,比用多个市场的低质量数据更有说服力。
项目需要有盈利的策略吗?+
不需要。一个严谨地证明某个因子"不显著"的研究,比一个充满 look-ahead bias 的"盈利策略"更有价值。面试官要看的是你的研究方法,不是你的 P&L。
需要做几个项目?+
一个深度项目远胜于三个浅层项目。建议集中精力做好一个,确保你能回答关于它的任何问题,然后再考虑扩展。
项目应该用 Python 还是 R?+
量化求职项目建议用 Python。Python 在量化行业的使用率远高于 R,而且 pandas、numpy、scipy 等库的生态更成熟。如果你的项目涉及统计建模,可以使用 statsmodels 或 scikit-learn,这些都是量化公司常用的工具。
如何证明项目结果的统计显著性?+
至少需要报告:IC 的 t 统计量(或 p 值)、样本外验证结果、不同时间段的稳健性检验。避免只报告样本内的漂亮数字——面试官会立刻追问样本外表现。
Free Resource
免费领取【Quant Finance 求职全套资料包】
包含 CV 模板、面试题库、Networking 模板信及完整的求职 Timeline。已有 1,200+ 学员领取。