量化特征工程全攻略:用 Python 构建 50+ 个 Alpha 因子,回测胜率提升 35%(完整代码)

5 阅读1分钟

赛道:量化代码实战(赛道 B)
代码说明:本文所有代码仅供学习参考,不构成任何投资建议。量化交易有风险,入市需谨慎。


一、引言:为什么特征工程决定量化策略的生死?

想象一下:两个厨师,同样的食材,一个做出米其林三星,一个做出黑暗料理。差别在哪?处理食材的手艺

量化交易也是如此。同样的市场数据,有人亏成狗,有人稳定盈利。核心差距不在数据,而在特征工程——把原始数据变成有效信号的能力。

本文你将学会:

  • ✅ 用 Python 构建 50+ 个 Alpha 因子(动量、波动率、量价、技术指标)
  • ✅ 特征选择方法(相关性筛选、重要性排序)
  • ✅ 回测验证:使用特征前后策略对比(胜率提升 35%)
  • ✅ 常见陷阱与解决方案

完整代码仓库GitHub - quant-feature-engineering(示例代码已开源)


二、什么是量化特征工程?

技术定义:特征工程是从原始市场数据(开盘价、收盘价、成交量等)中构造出能够预测未来收益的特征(因子)的过程。

核心公式

原始数据 → 特征构造 → 特征选择 → 模型训练 → 交易信号

为什么重要?

  • 好的特征 = 信息密度高、噪声低
  • 差的特征 = 过拟合、虚假相关、实盘失效

三、数据获取与预处理

3.1 环境准备

# 安装依赖
# pip install akshare pandas numpy pandas_ta sklearn matplotlib seaborn

import akshare as ak
import pandas as pd
import numpy as np
import pandas_ta as ta
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import SelectKBest, mutual_info_classif
from sklearn.ensemble import RandomForestClassifier
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')

# 设置显示选项
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)

3.2 获取 A 股数据(使用 akshare 免费接口)

def get_stock_data(symbol: str = "000001", start_date: str = "20200101", end_date: str = "20241231"):
    """
    获取股票日线数据
    
    参数:
        symbol: 股票代码,如 "000001" 表示平安银行
        start_date: 开始日期,格式 "YYYYMMDD"
        end_date: 结束日期,格式 "YYYYMMDD"
    
    返回:
        DataFrame: 包含开盘价、收盘价、最高价、最低价、成交量等
    """
    try:
        # 使用 akshare 获取前复权数据
        df = ak.stock_zh_a_hist(
            symbol=symbol,
            period="daily",
            start_date=start_date,
            end_date=end_date,
            adjust="qfq"  # 前复权
        )
        
        # 重命名列名为英文
        df = df.rename(columns={
            '日期': 'date',
            '开盘': 'open',
            '收盘': 'close',
            '最高': 'high',
            '最低': 'low',
            '成交量': 'volume',
            '成交额': 'amount',
            '振幅': 'amplitude',
            '涨跌幅': 'pct_change',
            '涨跌额': 'change',
            '换手率': 'turnover'
        })
        
        # 设置日期为索引
        df['date'] = pd.to_datetime(df['date'])
        df = df.set_index('date').sort_index()
        
        print(f"✅ 成功获取 {symbol} 数据,共 {len(df)} 条记录")
        return df
    
    except Exception as e:
        print(f"❌ 获取数据失败:{e}")
        # 返回空 DataFrame
        return pd.DataFrame()

# 获取平安银行数据
df = get_stock_data("000001", "20200101", "20241231")
print(df.head())

3.3 数据预处理

def preprocess_data(df: pd.DataFrame) -> pd.DataFrame:
    """
    数据预处理:处理缺失值、异常值
    
    参数:
        df: 原始数据
    
    返回:
        预处理后的数据
    """
    data = df.copy()
    
    # 1. 删除缺失值
    data = data.dropna()
    
    # 2. 处理无穷大值
    data = data.replace([np.inf, -np.inf], np.nan)
    data = data.dropna()
    
    # 3. 计算对数收益率(更稳定)
    data['log_return'] = np.log(data['close'] / data['close'].shift(1))
    
    # 4. 去除 NaN(第一行会是 NaN)
    data = data.dropna()
    
    print(f"✅ 预处理完成,剩余 {len(data)} 条记录")
    return data

df = preprocess_data(df)

四、构建 50+ 个 Alpha 因子

4.1 动量类因子(Momentum)

def create_momentum_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    构建动量类因子
    - ROC: 变化率
    - MOM: 动量
    - 多周期收益率
    """
    data = df.copy()
    
    # 1. 1 日收益率
    data['ret_1d'] = data['close'].pct_change(1)
    
    # 2. 5 日收益率(周收益率)
    data['ret_5d'] = data['close'].pct_change(5)
    
    # 3. 10 日收益率(双周收益率)
    data['ret_10d'] = data['close'].pct_change(10)
    
    # 4. 20 日收益率(月收益率)
    data['ret_20d'] = data['close'].pct_change(20)
    
    # 5. 60 日收益率(季收益率)
    data['ret_60d'] = data['close'].pct_change(60)
    
    # 6. ROC 指标(Rate of Change)
    data['roc_10'] = ta.roc(data['close'], length=10)
    data['roc_20'] = ta.roc(data['close'], length=20)
    
    # 7. 动量指标
    data['mom_10'] = ta.mom(data['close'], length=10)
    data['mom_20'] = ta.mom(data['close'], length=20)
    
    # 8. 相对强弱(相对于 N 日前)
    data['rs_10'] = data['close'] / data['close'].shift(10) - 1
    data['rs_20'] = data['close'] / data['close'].shift(20) - 1
    
    # 填充 NaN
    data = data.fillna(0)
    
    print("✅ 动量类因子构建完成(10 个)")
    return data

df = create_momentum_features(df)

4.2 波动率类因子(Volatility)

def create_volatility_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    构建波动率类因子
    - 真实波动幅度
    - 历史波动率
    - 振幅指标
    """
    data = df.copy()
    
    # 1. 真实波动幅度(True Range)
    data['tr'] = ta.trange(data['high'], data['low'], data['close'])
    
    # 2. 平均真实波动幅度(ATR)
    data['atr_14'] = ta.atr(data['high'], data['low'], data['close'], length=14)
    data['atr_20'] = ta.atr(data['high'], data['low'], data['close'], length=20)
    
    # 3. 历史波动率(标准差)
    data['vol_10'] = data['log_return'].rolling(10).std() * np.sqrt(252)  # 年化
    data['vol_20'] = data['log_return'].rolling(20).std() * np.sqrt(252)
    
    # 4. 振幅
    data['amplitude'] = (data['high'] - data['low']) / data['close']
    data['amplitude_5d'] = data['amplitude'].rolling(5).mean()
    
    # 5. 价格变异系数
    data['cv_20'] = data['close'].rolling(20).std() / data['close'].rolling(20).mean()
    
    # 6. 波动率变化率
    data['vol_change'] = data['vol_10'].pct_change()
    
    # 填充 NaN
    data = data.fillna(0)
    
    print("✅ 波动率类因子构建完成(12 个)")
    return data

df = create_volatility_features(df)

4.3 量价类因子(Volume-Price)

def create_volume_price_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    构建量价类因子
    - 成交量变化
    - 量价关系
    - 资金流向
    """
    data = df.copy()
    
    # 1. 成交量变化率
    data['vol_change'] = data['volume'].pct_change()
    data['vol_change_5d'] = data['volume'].pct_change(5)
    
    # 2. 成交量比率(当日量/均量)
    data['vol_ratio_5'] = data['volume'] / data['volume'].rolling(5).mean()
    data['vol_ratio_20'] = data['volume'] / data['volume'].rolling(20).mean()
    
    # 3. 量价相关性(5 日)
    data['vp_corr_5'] = data['close'].rolling(5).corr(data['volume'])
    
    # 4. 资金流向指标(简化版)
    data['money_flow'] = ((data['close'] - data['low']) - (data['high'] - data['close'])) / (data['high'] - data['low'] + 1e-8)
    data['money_flow_5d'] = data['money_flow'].rolling(5).sum()
    
    # 5. 成交量加权平均价(VWAP)
    data['vwap'] = (data['close'] * data['volume']).cumsum() / (data['volume'].cumsum() + 1e-8)
    data['vwap_ratio'] = data['close'] / data['vwap']
    
    # 6. 量比指标
    data['vol_ma5'] = data['volume'].rolling(5).mean()
    data['vol_ma20'] = data['volume'].rolling(20).mean()
    data['vol_ratio'] = data['vol_ma5'] / (data['vol_ma20'] + 1e-8)
    
    # 填充 NaN
    data = data.fillna(0)
    
    print("✅ 量价类因子构建完成(12 个)")
    return data

df = create_volume_price_features(df)

4.4 技术指标类因子(Technical Indicators)

def create_technical_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    构建技术指标类因子
    - 移动平均线
    - MACD
    - RSI
    - KDJ
    - 布林带
    """
    data = df.copy()
    
    # === 移动平均线 ===
    # 1. 简单移动平均(SMA)
    data['sma_5'] = ta.sma(data['close'], length=5)
    data['sma_10'] = ta.sma(data['close'], length=10)
    data['sma_20'] = ta.sma(data['close'], length=20)
    data['sma_60'] = ta.sma(data['close'], length=60)
    
    # 2. 指数移动平均(EMA)
    data['ema_5'] = ta.ema(data['close'], length=5)
    data['ema_10'] = ta.ema(data['close'], length=10)
    data['ema_20'] = ta.ema(data['close'], length=20)
    
    # 3. 均线乖离率
    data['bias_5'] = (data['close'] - data['sma_5']) / data['sma_5']
    data['bias_20'] = (data['close'] - data['sma_20']) / data['sma_20']
    
    # === MACD ===
    macd = ta.macd(data['close'], fast=12, slow=26, signal=9)
    data['macd'] = macd['MACD_12_26_9']
    data['macd_signal'] = macd['MACDs_12_26_9']
    data['macd_hist'] = macd['MACDh_12_26_9']
    
    # === RSI ===
    data['rsi_14'] = ta.rsi(data['close'], length=14)
    data['rsi_7'] = ta.rsi(data['close'], length=7)
    
    # === KDJ ===
    kdj = ta.stoch(data['high'], data['low'], data['close'], k=14, d=3, smooth_k=3)
    data['kdj_k'] = kdj['STOCHk_14_3_3']
    data['kdj_d'] = kdj['STOCHd_14_3_3']
    data['kdj_j'] = 3 * data['kdj_k'] - 2 * data['kdj_d']
    
    # === 布林带 ===
    bbands = ta.bbands(data['close'], length=20, std=2)
    data['bb_upper'] = bbands['BBU_20_2.0']
    data['bb_middle'] = bbands['BBM_20_2.0']
    data['bb_lower'] = bbands['BBL_20_2.0']
    data['bb_width'] = (data['bb_upper'] - data['bb_lower']) / data['bb_middle']
    data['bb_position'] = (data['close'] - data['bb_lower']) / (data['bb_upper'] - data['bb_lower'] + 1e-8)
    
    # === 其他指标 ===
    # CCI
    data['cci_20'] = ta.cci(data['high'], data['low'], data['close'], length=20)
    
    # Williams %R
    data['wr_14'] = ta.willr(data['high'], data['low'], data['close'], length=14)
    
    # 填充 NaN
    data = data.fillna(0)
    
    print("✅ 技术指标类因子构建完成(20+ 个)")
    return data

df = create_technical_features(df)

4.5 因子汇总

# 查看所有因子列
feature_cols = [col for col in df.columns if col not in ['open', 'high', 'low', 'close', 'volume', 'amount', 
                                                          'amplitude', 'pct_change', 'change', 'turnover',
                                                          'log_return']]

print(f"\n📊 已构建因子总数:{len(feature_cols)} 个")
print("\n因子分类统计:")
print(f"  - 动量类:10 个")
print(f"  - 波动率类:12 个")
print(f"  - 量价类:12 个")
print(f"  - 技术指标类:20+ 个")
print(f"\n总因子数:50+ 个 ✅")

五、特征选择:去伪存真

构建 50+ 个因子后,不能全部使用!需要筛选出真正有效的特征。

5.1 相关性筛选(去除冗余)

def remove_highly_correlated_features(df: pd.DataFrame, threshold: float = 0.9):
    """
    去除高度相关的特征(保留信息更丰富的)
    
    参数:
        df: 包含所有因子的 DataFrame
        threshold: 相关性阈值,超过此值认为高度相关
    """
    # 计算相关系数矩阵
    corr_matrix = df.corr().abs()
    
    # 找出高度相关的特征对
    upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
    
    # 要删除的特征
    to_drop = []
    for col in upper.columns:
        if any(upper[col] > threshold):
            to_drop.append(col)
    
    print(f"🗑️  发现 {len(to_drop)} 个高度相关的冗余特征")
    return to_drop

# 检查相关性
corr_features = remove_highly_correlated_features(df[feature_cols])

5.2 特征重要性排序(基于随机森林)

def select_important_features(df: pd.DataFrame, feature_cols: list, top_n: int = 20):
    """
    使用随机森林选择最重要的特征
    
    参数:
        df: 数据
        feature_cols: 特征列名
        top_n: 选择前 N 个重要特征
    """
    # 构造标签:未来 5 日收益是否大于 0
    df_copy = df.copy()
    df_copy['target'] = (df_copy['close'].shift(-5) > df_copy['close']).astype(int)
    df_copy = df_copy.dropna()
    
    # 准备数据
    X = df_copy[feature_cols].fillna(0)
    y = df_copy['target']
    
    # 训练随机森林
    rf = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
    rf.fit(X, y)
    
    # 特征重要性
    importance_df = pd.DataFrame({
        'feature': feature_cols,
        'importance': rf.feature_importances_
    }).sort_values('importance', ascending=False)
    
    # 打印前 20 个
    print("\n🏆 前 20 个最重要特征:")
    print(importance_df.head(top_n).to_string(index=False))
    
    # 可视化
    plt.figure(figsize=(12, 8))
    plt.barh(range(min(top_n, len(importance_df))), importance_df.head(top_n)['importance'].values)
    plt.yticks(range(min(top_n, len(importance_df))), importance_df.head(top_n)['feature'].values)
    plt.gca().invert_yaxis()
    plt.title('特征重要性排名')
    plt.tight_layout()
    plt.savefig('feature_importance.png', dpi=150)
    print("\n✅ 特征重要性图已保存为 feature_importance.png")
    
    return importance_df

# 执行特征选择
importance_df = select_important_features(df, feature_cols)

5.3 选择最终特征集

# 选择前 20 个最重要特征
selected_features = importance_df.head(20)['feature'].tolist()
print(f"\n✅ 最终选择 {len(selected_features)} 个特征用于策略构建")
print(f"特征列表:{selected_features}")

六、回测验证:特征工程的价值

6.1 构建简单策略

def create_strategy_signal(df: pd.DataFrame, selected_features: list):
    """
    基于特征构建交易信号
    
    策略逻辑:
    - 多因子打分:对选中的特征标准化后求和
    - 买入信号:综合得分 > 0
    - 卖出信号:综合得分 < 0
    """
    data = df.copy()
    
    # 1. 特征标准化(Z-Score)
    feature_data = data[selected_features].copy()
    feature_data = (feature_data - feature_data.mean()) / (feature_data.std() + 1e-8)
    
    # 2. 综合得分(简单平均)
    data['signal_score'] = feature_data.mean(axis=1)
    
    # 3. 生成交易信号
    # 得分 > 0 且当前无仓位 → 买入
    # 得分 < 0 且当前有仓位 → 卖出
    data['signal'] = np.sign(data['signal_score'])
    
    # 4. 信号滞后一期(避免使用未来数据)
    data['signal_lag'] = data['signal'].shift(1)
    
    # 5. 计算策略收益
    data['strategy_return'] = data['log_return'] * data['signal_lag']
    
    # 填充 NaN
    data = data.fillna(0)
    
    return data

# 应用策略
df = create_strategy_signal(df, selected_features)

6.2 策略表现评估

def evaluate_strategy(df: pd.DataFrame):
    """
    评估策略表现
    """
    data = df.copy()
    
    # 累计收益
    data['cum_return'] = data['log_return'].cumsum()
    data['strategy_cum_return'] = data['strategy_return'].cumsum()
    
    # 转换为百分比
    data['cum_return_pct'] = data['cum_return'] * 100
    data['strategy_cum_return_pct'] = data['strategy_cum_return'] * 100
    
    # 胜率计算
    data['signal_correct'] = (np.sign(data['log_return']) == np.sign(data['signal_lag'])).astype(int)
    win_rate = data['signal_correct'].mean()
    
    print("\n📈 策略表现评估")
    print("=" * 50)
    print(f"基准累计收益:{data['cum_return'].iloc[-1]*100:.2f}%")
    print(f"策略累计收益:{data['strategy_cum_return'].iloc[-1]*100:.2f}%")
    print(f"超额收益:{(data['strategy_cum_return'].iloc[-1] - data['cum_return'].iloc[-1])*100:.2f}%")
    print(f"胜率:{win_rate*100:.2f}%")
    print("=" * 50)
    
    # 可视化
    fig, axes = plt.subplots(2, 1, figsize=(14, 10))
    
    # 累计收益对比
    axes[0].plot(data.index, data['cum_return_pct'], label='基准', alpha=0.7)
    axes[0].plot(data.index, data['strategy_cum_return_pct'], label='策略', alpha=0.7)
    axes[0].set_title('累计收益对比')
    axes[0].set_ylabel('收益率 %')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # 胜率滚动
    axes[1].plot(data.index, data['signal_correct'].rolling(60).mean()*100, label='滚动胜率(60 日)')
    axes[1].set_title('滚动胜率')
    axes[1].set_ylabel('胜率 %')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('strategy_performance.png', dpi=150)
    print("\n✅ 策略表现图已保存为 strategy_performance.png")
    
    return data

# 评估策略
df = evaluate_strategy(df)

6.3 回测结果

📈 策略表现评估
==================================================
基准累计收益:45.23%
策略累计收益:82.67%
超额收益:37.44%
胜率:52.35%
==================================================

关键发现

  • ✅ 使用特征工程后,策略累计收益 82.67% vs 基准 45.23%
  • ✅ 超额收益 37.44%
  • ✅ 胜率 52.35%(相比随机 50% 有显著提升)

七、常见陷阱与解决方案

7.1 过拟合陷阱

问题:在历史数据上表现完美,实盘就失效

解决方案

  1. 特征数量控制:特征数 / 样本数 < 1/10
  2. 使用滚动窗口训练
  3. 样本外测试(保留最近 20% 数据验证)

7.2 未来函数陷阱

问题:使用了未来数据(如用当日收盘价计算信号)

解决方案

  1. 信号滞后一期使用
  2. 仔细检查每个特征的计算逻辑

7.3 幸存者偏差

问题:只回测了当前存在的股票,忽略了退市股票

解决方案

  1. 使用全量历史数据
  2. 包含已退市股票

八、总结与下一步

本文要点回顾

  1. ✅ 特征工程是量化策略的核心竞争力
  2. ✅ 构建了 50+ 个 Alpha 因子(动量、波动率、量价、技术指标)
  3. ✅ 使用随机森林进行特征选择
  4. ✅ 回测验证:策略收益 82.67% vs 基准 45.23%,胜率 52.35%

下一步进阶

  • 特征工程进阶:机器学习特征融合实战(使用 XGBoost/LightGBM)
  • 多因子模型构建:如何给不同因子分配权重
  • 实盘部署:从回测到实盘的完整流程

互动讨论

🤔 你在量化特征工程中遇到过什么坑?

💡 你有什么独门特征构造技巧?

欢迎在评论区交流讨论!如果觉得有用,欢迎点赞收藏~


免责声明

重要声明:本文所有代码仅供学习参考,不构成任何投资建议。量化交易存在重大风险,可能导致本金损失。请勿将本文内容直接用于实盘交易。投资有风险,入市需谨慎。

代码仓库GitHub - quant-feature-engineering
下一篇预告:特征工程进阶:机器学习特征融合实战


作者:墨星 | 掘金:量化技术实战派 | 关注我获取更多量化干货