从零搭建量化求职项目作品集:让简历上的 GitHub 真正说话
90% 的量化求职者简历上写着"熟悉 Python、Pandas、Backtrader",但面试官一问项目细节就哑口无言。本文手把手教你从选题到代码结构,搭建一个能在面试中讲清楚、经得起追问的量化项目。
## 为什么大多数量化项目在面试中毫无价值
打开任何一个"量化求职者"的 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) 对齐 | | 复权处理 | 使用前复权价格 | 改用后复权价格 |
以下是数据清洗的核心代码框架,展示如何正确处理前视偏差和幸存者偏差: ```python import pandas as pd import numpy as np import akshare as ak # ─── Step 1: 获取历史股票池(含退市股票,避免幸存者偏差)───────────────── def get_universe_at_date(date: str, data_dir: str = './data/') -> list[str]: """ 返回指定日期的股票池(包含当时已上市且未退市的所有股票) """ listing_df = pd.read_csv(f'{data_dir}stock_listing_history.csv', parse_dates=['list_date', 'delist_date']) target_date = pd.Timestamp(date) universe = listing_df[ (listing_df['list_date'] <= target_date) & ((listing_df['delist_date'].isna()) | (listing_df['delist_date'] > target_date)) ]['stock_code'].tolist() return universe # ─── Step 2: 正确对齐信号与收益(避免前视偏差)────────────────────────── def compute_forward_returns(prices: pd.DataFrame, holding_period: int = 5) -> pd.DataFrame: """ 计算未来 N 日收益率(信号日 T 的收益 = T+1 开盘到 T+N+1 开盘的涨跌幅) 使用 shift(-holding_period) 确保不存在前视偏差 """ # 使用开盘价成交,避免收盘价前视偏差 open_prices = prices['open'] # T 日信号 → T+1 开盘买入 → T+1+N 开盘卖出 fwd_returns = open_prices.shift(-1 - holding_period) / open_prices.shift(-1) - 1 return fwd_returns # ─── Step 3: 数据质量检查 ────────────────────────────────────────────────── def data_quality_check(df: pd.DataFrame) -> dict: report = {} # 缺失值比例 report['missing_rate'] = df.isnull().mean().to_dict() # 异常值(价格为 0 或负数) if 'close' in df.columns: report['zero_price_count'] = (df['close'] <= 0).sum() # 检查收益率极端值(单日涨跌幅超过 ±20% 视为可疑) if 'close' in df.columns: daily_ret = df['close'].pct_change() report['extreme_returns'] = (daily_ret.abs() > 0.21).sum() return report # 使用示例 if __name__ == '__main__': universe = get_universe_at_date('2023-01-01') 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 分析代码,这是面试中最常被追问的部分: ```python import pandas as pd import numpy as np from scipy import stats import matplotlib.pyplot as plt # ─── 换手率反转因子计算 ────────────────────────────────────────────────────── def compute_turnover_reversal_factor( turnover: pd.DataFrame, # 日频换手率,index=date, columns=stock_code lookback: int = 20 # 过去 N 日均换手率 ) -> pd.DataFrame: """ 计算换手率反转因子:过去 lookback 日均换手率(越高分越高) 因子方向:做空高换手率股票(预期反转) """ # 截面标准化:每个截面日期内进行 z-score 标准化 factor = turnover.rolling(lookback).mean() # 截面 z-score(消除时序趋势) factor_zscore = factor.apply( lambda row: (row - row.mean()) / (row.std() + 1e-8), axis=1 ) return factor_zscore # ─── IC 分析 ──────────────────────────────────────────────────────────────── def compute_ic_series( factor: pd.DataFrame, forward_returns: pd.DataFrame ) -> pd.Series: """ 计算每个截面日期的 IC(Spearman 相关系数) IC_t = Spearman(factor_{t}, return_{t+1:t+N}) """ ic_series = {} for date in factor.index: if date not in forward_returns.index: continue f = factor.loc[date].dropna() r = forward_returns.loc[date].dropna() common = f.index.intersection(r.index) if len(common) < 30: # 至少需要 30 只股票 continue ic, _ = stats.spearmanr(f[common], r[common]) ic_series[date] = ic return pd.Series(ic_series) # ─── 汇总报告 ──────────────────────────────────────────────────────────────── def ic_summary_report(ic_series: pd.Series) -> dict: return { 'IC Mean': round(ic_series.mean(), 4), 'IC Std': round(ic_series.std(), 4), 'IR (IC/ICSD)': round(ic_series.mean() / ic_series.std(), 4), 'IC > 0 Ratio': round((ic_series > 0).mean(), 4), 't-statistic': round(stats.ttest_1samp(ic_series.dropna(), 0).statistic, 4), 'p-value': round(stats.ttest_1samp(ic_series.dropna(), 0).pvalue, 6), } # 使用示例 if __name__ == '__main__': # 假设 turnover_df 和 fwd_returns_df 已经准备好 factor = compute_turnover_reversal_factor(turnover_df, lookback=20) ic = compute_ic_series(factor, fwd_returns_df) report = ic_summary_report(ic) print(pd.Series(report).to_string()) # 输出示例: # IC Mean -0.0312 ← 负值说明高换手率 → 未来负收益(符合反转假设) # IR (IC/ICSD) -0.7841 ← 绝对值 > 0.5,因子可用 # IC > 0 Ratio 0.3210 ← 约 32% 的时间因子方向反转,需关注 # 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 提供一对一的项目辅导,导师均为顶级量化机构的现任研究员,帮助学员从选题、数据处理到最终呈现,打造真正能在面试中说话的量化项目作品集。
没有彭博或 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 值)、样本外验证结果、不同时间段的稳健性检验。避免只报告样本内的漂亮数字——面试官会立刻追问样本外表现。
AT&T Career