查看全课大纲(第 3 / 52 节)
  1. 1.量化分析基本概念:从咖啡馆到华尔街
  2. 2.量化交易员学习路线:从零基础到策略开发
  3. 3.量化策略生命周期:数据分析的五大流程
  4. 4.量化数据类型:数值型与类别型数据
  5. 5.量化沙盒搭建(1):Python与Anaconda安装
  6. 6.量化沙盒搭建(2):Jupyter Notebook与库配置
  7. 7.本地多源数据:量化历史回测的基石
  8. 8.TXT数据流操作:处理金融交易日志
  9. 9.JSON数据流操作:解析股票行情API响应
  10. 10.CSV行情文件操作:读取与存储历史K线
  11. 11.Excel报表操作:清洗财报与财务指标
  12. 12.量化数据库:SQL常用查询语法
  13. 13.量化数据库:Python连接与写入
  14. 14.量化多表连接:关联股票信息与行情
  15. 15.实战:小象咖啡店营业数据与个股财务指标的多源数据分析
  16. 16.量化数据采集:爬虫工作原理与合规
  17. 17.量化爬虫核心:URL管理与去重策略
  18. 18.量化网页下载:防反爬与请求头伪装
  19. 19.网页解析(1):使用XPath定位财经新闻
  20. 20.网页解析(2):使用CSS选择器与ItemLoader
  21. 21.Scrapy框架:构建工业级量化爬虫
  22. 22.Scrapy实战(1):新建项目与数据模型定义
  23. 23.Scrapy实战(2):编写财经爬虫逻辑
  24. 24.Scrapy实战(3):启用数据管道清洗行情
  25. 25.Scrapy实战(4):防反爬中间件配置
  26. 26.实战:构建每日热门个股行情自动采集系统
  27. 27.NumPy与SciPy:加速量化矩阵计算
  28. 28.NumPy数组:构建股票价格矩阵
  29. 29.数组切片:提取特定股票与交易日数据
  30. 30.NumPy常用方法:计算均值与波动率
  31. 31.向量化计算:摆脱缓慢的For循环
  32. 32.通用函数ufunc:计算对数收益率与累计收益
  33. 33.实战:股票组合资产收益率与协方差矩阵计算
  34. 34.Pandas Series:管理单只股票收盘价
  35. 35.Pandas DataFrame:构建多只股票行情面板
  36. 36.Pandas Index:时间序列索引与对齐
  37. 37.DataFrame索引操作:loc与iloc定位行情
  38. 38.索引总结:量化多字段筛选与定位
  39. 39.运算与对齐:合并不同交易日的数据
  40. 40.Pandas map:对股票代码与状态做映射
  41. 41.Pandas apply:滚动计算技术指标
  42. 42.排序操作:按市盈率与收益率对股票排名
  43. 43.数据清洗:填充与剔除停牌股票缺失值
  44. 44.数据清洗:剔除重复报价与异常交易数据
  45. 45.常用统计:量化最大回撤与分位数风险评估
  46. 46.极值定位:寻找历史最高价与最低价的发生日
  47. 47.风险与累计收益:计算年化波动率与累计回报
  48. 48.实战:小象咖啡店营业数据与热门个股时间序列清洗分析
  49. 49.Series索引操作:按位置与标签访问行情数据
  50. 50.文件读写操作:CSV行情数据导入导出
  51. 51.数据清洗:替换异常报价数据
  52. 52.常用统计:sum/mean/median/count聚合指标

量化策略生命周期:数据分析的五大流程

约 12 分钟

小象实战讲义 · 量化交易Python入门:从咖啡馆到华尔街

一个量化策略从想法到落地,中间要经历哪些环节?本节把完整生命周期拆解为数据分析的五大流程——问题定义、数据采集、清洗处理、分析建模、结论落地,并说明每个环节的产出物与常见坑。理解这条主线后,后面章节的每个工具你都知道它服务于流程的哪一步,而不是孤立地学语法。

💡 核心导读

本节将系统介绍数据分析的标准流程,从数据获取到最终报告生成的全生命周期。通过完整的Python代码演示,您将掌握:数据采集与清洗的核心方法、探索性分析(EDA)的典型技术、建模分析的关键步骤,以及如何通过可视化呈现分析结果。重点学习Pandas数据透视、Matplotlib/seaborn可视化,以及scikit-learn的快速建模技巧。


数据分析标准流程

1. 数据采集与输入

import pandas as pd
import numpy as np

# 构造一个简单的股票收盘价序列
data = {
    'date': pd.date_range('2024-01-01', periods=6),
    'symbol': ['AAPL', 'AAPL', 'AAPL', 'GOOGL', 'GOOGL', 'GOOGL'],
    'close': [175.2, 176.5, np.nan, 142.3, 143.8, 145.0],
    'volume': [78000000, 82000000, 79000000, 32000000, 31000000, 33000000]
}
df = pd.DataFrame(data)

print(f"数据维度: {df.shape}")
print("\n前3行数据示例:")
print(df.head(3).to_string())

输出示例(ASCII表格格式):

数据维度: (6, 4)

前3行数据示例:
        date symbol  close    volume
0 2024-01-01   AAPL  175.2  78000000
1 2024-01-02   AAPL  176.5  82000000
2 2024-01-03   AAPL    NaN  79000000

2. 数据清洗与预处理

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

# 构造一个简单的金融数据集用于演示数据清洗
np.random.seed(42)  # 固定随机种子,保证输出确定性
dates = pd.date_range('2024-01-01', periods=10)
data = {
    'date': dates,
    'returns': np.random.randn(10) * 0.02 + 0.001,  # 日收益率
    'volume': np.random.randint(1000000, 5000000, 10),  # 成交量
    'volatility': np.random.rand(10) * 0.05 + 0.01  # 波动率
}
df = pd.DataFrame(data)

# 引入一个异常值
df.loc[5, 'returns'] = 0.15  # 一个异常高的收益率

df_clean = df.copy()
# 处理异常值:将收益率限制在[-0.1, 0.1]之间
df_clean['returns'] = df_clean['returns'].clip(lower=-0.1, upper=0.1)

# 标准化两个特征
scaler = StandardScaler()
df_clean[['volume', 'volatility']] = scaler.fit_transform(df_clean[['volume', 'volatility']])

print("\n清洗后统计摘要(部分):")
print(df_clean[['returns', 'volume', 'volatility']].describe().loc[['mean', 'std']].to_string())
清洗后统计摘要(部分):
       returns        volume    volatility
mean  0.020329 -1.110223e-16  1.665335e-16
std   0.031141  1.054093e+00  1.054093e+00

3. 探索性分析(EDA)

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

# 构造并处理一个简单的金融特征数据集
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=8)
data = {
    'date': dates,
    'momentum': np.random.randn(8) * 0.1 + 0.02,
    'value': np.random.randn(8) * 0.08 + 0.01,
    'size': np.random.randn(8) * 0.12 - 0.01,
    'target_return': np.random.randn(8) * 0.02 + 0.005
}
df = pd.DataFrame(data)
df_clean = df.copy()

# 标准化特征
scaler = StandardScaler()
df_clean[['momentum', 'value', 'size']] = scaler.fit_transform(df_clean[['momentum', 'value', 'size']])

fig1, ax1 = plt.subplots(figsize=(8, 6))
corr_matrix = df_clean[['momentum', 'value', 'size', 'target_return']].corr()
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', ax=ax1, fmt='.2f')
ax1.set_title('因子相关性矩阵')
print("已生成特征相关性热图。")
plt.close(fig1)

fig2, ax2 = plt.subplots(figsize=(8, 4))
df_clean[['momentum', 'value']].boxplot(vert=False, ax=ax2)
ax2.set_title('关键因子分布(动量、价值)')
print("已生成关键特征箱线图。")
plt.close(fig2)
已生成特征相关性热图。
已生成关键特征箱线图。

4. 建模与分析

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler

# 构造一个简单的多因子预测收益率的数据集
np.random.seed(42)
n_samples = 20
data = {
    'factor_momentum': np.random.randn(n_samples) * 0.1,
    'factor_value': np.random.randn(n_samples) * 0.08,
    'factor_quality': np.random.randn(n_samples) * 0.06,
    'next_day_return': 0.005 + 0.3 * np.random.randn(n_samples) * 0.02  # 目标变量
}
df = pd.DataFrame(data)

# 数据清洗与标准化
df_clean = df.copy()
scaler = StandardScaler()
df_clean[['factor_momentum', 'factor_value', 'factor_quality']] = scaler.fit_transform(df_clean[['factor_momentum', 'factor_value', 'factor_quality']])

X = df_clean.drop('next_day_return', axis=1)
y = df_clean['next_day_return']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

model = LinearRegression()
model.fit(X_train, y_train)

print(f"模型R2分数: {model.score(X_test, y_test):.3f}")
print("因子系数(重要性):")
for name, coef in zip(X.columns, model.coef_):
    print(f"- {name}: {coef:.4f}")
模型R2分数: -0.825
因子系数(重要性):
- factor_momentum: -0.0006
- factor_value: 0.0001
- factor_quality: -0.0019

5. 结果可视化

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler

# 构造、处理并建模
np.random.seed(42)
n_samples = 20
data = {
    'factor_A': np.random.randn(n_samples) * 0.1,
    'factor_B': np.random.randn(n_samples) * 0.08,
    'target_return': 0.005 + 0.3 * np.random.randn(n_samples) * 0.02
}
df = pd.DataFrame(data)
df_clean = df.copy()
scaler = StandardScaler()
df_clean[['factor_A', 'factor_B']] = scaler.fit_transform(df_clean[['factor_A', 'factor_B']])

X = df_clean.drop('target_return', axis=1)
y = df_clean['target_return']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)

model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

fig, ax = plt.subplots(figsize=(6, 6))
ax.scatter(y_test, y_pred, alpha=0.7)
y_min, y_max = y_test.min(), y_test.max()
ax.plot([y_min, y_max], [y_min, y_max], 'r--', lw=2)
ax.set_xlabel('真实收益率')
ax.set_ylabel('预测收益率')
ax.set_title('收益率预测结果对比')
print("已生成预测结果对比散点图。")
plt.close(fig)
已生成预测结果对比散点图。

📝 动手练一练

第 1 题 · 概念应用

小象在分析一个“动量反转”策略时,发现直接使用原始日度收益率序列计算出的20日动量因子,在回测中表现异常优秀。但当他将数据按日期排序后,仔细检查了数据清洗前的原始行情数据,发现其中一些股票在特定日期有长时间的停牌(缺失值)。以下哪一种缺失值处理方式会在动量因子中引入未来函数(前视偏差),是他必须避免的?

  • A. 在计算20日收益率(动量)时,直接跳过缺失日,用停牌日之后第一个有效交易日的价格进行计算,以保证计算窗口长度固定为20个交易日。
  • B. 在数据清洗阶段,使用 .bfill()(后向填充),用停牌日之后第一个有效交易日的价格填充缺失的收盘价,然后再计算动量因子。
  • C. 在数据清洗阶段,将停牌日的收益率视为0(即假定当日无损益),然后计算动量因子。
  • D. 在数据清洗阶段,使用 .ffill()(前向填充),用停牌日前最后一个有效交易日的价格填充缺失的收盘价,然后计算动量因子。
👉 点击查看参考答案

答案:B

推理过程:判断关键在于该处理是否使用了计算时点之后才能知道的价格。选项B的后向填充(.bfill())拿停牌之后的复牌价回填历史缺口,直接把未来信息注入因子,是典型的未来函数,会严重夸大回测性能,必须避免。其余三项都不使用未来价格:选项A跳过缺失日会造成窗口长度与日期对齐的口径问题,但不引入未来信息;选项C把停牌日收益率记为 0 是一种建模假设(假定当日无损益),可能扭曲收益率分布,但同样没有偷看未来;选项D的前向填充用停牌前最后一个有效价格,模拟“停牌期间无法交易、价格保持不变”,是量化回测处理停牌缺失值的标准做法。

💡 小象贴士:时刻警惕“未来函数”!在数据清洗和特征计算中,任何使用到未来信息(如用明天的价格填充今天的缺失值)的操作,都会导致回测结果严重失真,产生无法在实际中复现的虚假收益。


第 2 题 · 代码填空

在构建因子库时,需要对原始因子值进行“去极值”处理,以防止少数异常值(如财报公布日的暴涨暴跌)对后续标准化和模型训练产生过大影响。一种常见的方法是使用“MAD(Median Absolute Deviation,中位数绝对偏差)法”。请补全下面使用MAD法对因子序列进行去极值处理的代码。

import pandas as pd
import numpy as np

# 模拟一个包含异常值的因子序列(例如,市值因子)
np.random.seed(2024)
factor_values = np.random.randn(100) * 10 + 50  # 均值为50,标准差为10的正态分布
factor_values[10] = 200  # 第11个位置插入一个巨大的正异常值
factor_values[90] = -100 # 第91个位置插入一个巨大的负异常值

series = pd.Series(factor_values, name='factor_raw')
print("原始序列描述:")
print(series.describe())

# MAD去极值处理
median = series.median()
mad = (series - median).abs().median()  # 计算MAD

# 定义合理的上下限,通常取中位数 ± n倍 MAD,n常取3或5
n = 5
upper_limit = median + n * 1.4826 * mad  # 1.4826是正态分布下MAD与标准差的换算系数
lower_limit = median - n * 1.4826 * mad

# 将超出上下限的值替换为限值本身
series_winsorized = series.______(lower=lower_limit, upper=upper_limit)

print(f"\n中位数: {median:.2f}, MAD: {mad:.2f}")
print(f"下限: {lower_limit:.2f}, 上限: {upper_limit:.2f}")
print("\n去极值后序列描述:")
print(series_winsorized.describe())
👉 点击查看参考答案

空位应填:clip

补全后的完整可运行代码块:

import pandas as pd
import numpy as np

# 模拟一个包含异常值的因子序列(例如,市值因子)
np.random.seed(2024)
factor_values = np.random.randn(100) * 10 + 50  # 均值为50,标准差为10的正态分布
factor_values[10] = 200  # 第11个位置插入一个巨大的正异常值
factor_values[90] = -100 # 第91个位置插入一个巨大的负异常值

series = pd.Series(factor_values, name='factor_raw')
print("原始序列描述:")
print(series.describe())

# MAD去极值处理
median = series.median()
mad = (series - median).abs().median()  # 计算MAD

# 定义合理的上下限,通常取中位数 ± n倍 MAD,n常取3或5
n = 5
upper_limit = median + n * 1.4826 * mad  # 1.4826是正态分布下MAD与标准差的换算系数
lower_limit = median - n * 1.4826 * mad

# 将超出上下限的值替换为限值本身
series_winsorized = series.clip(lower=lower_limit, upper=upper_limit)

print(f"\n中位数: {median:.2f}, MAD: {mad:.2f}")
print(f"下限: {lower_limit:.2f}, 上限: {upper_limit:.2f}")
print("\n去极值后序列描述:")
print(series_winsorized.describe())

运行结果示例:

原始序列描述:
count    100.000000
mean      50.239623
std       23.516533
min     -100.000000
25%       42.946634
50%       50.269396
75%       58.220359
max      200.000000
Name: factor_raw, dtype: float64

中位数: 50.27, MAD: 7.88
下限: -8.11, 上限: 108.65

去极值后序列描述:
count    100.000000
mean      50.245010
std       12.935806
min       -8.111193
25%       42.946634
50%       50.269396
75%       58.220359
max      108.649985
Name: factor_raw, dtype: float64

可以看到,经过clip方法去极值处理后,序列的样本标准差从 23.52 回落到 12.94(生成数据时总体标准差设定为 10;未收敛到 10 是因为只有 100 个样本、且上下限内仍有偏度),人为置入的 -100 与 200 被限制到 -8.11 与 108.65,极端异常值的影响被有效控制。

本章小结

通过本节的实践,我们完成了从原始数据到建模分析的完整流程。以下是需要重点掌握的技能点:

📋 行动清单

  • 使用Pandas完成数据加载与清洗,尝试对AveBedrms字段进行缺失值填充
  • 用Seaborn绘制至少3种不同类型的图表(如pairplot、violinplot等)
  • 修改建模代码,尝试用随机森林(RandomForestRegressor)替代线性回归并比较结果

—— 小象教研组