Python 多因子 Alpha 策略全攻略:用 5 个因子构建选股模型,回测年化收益 18.5% 跑赢沪深 300(完整代码)

4 阅读1分钟

代码仅供学习参考,不构成投资建议。量化交易有风险,入市需谨慎。


一、为什么单因子策略越来越难赚钱?

2026 年,A 股市场有效性持续提升。很多量化爱好者发现:

曾经有效的单因子策略,现在越来越难赚钱了。

❌ 单因子策略常见问题:

- 市值因子:2020-2022 年小市值策略年化 35%,2023-2025 年降至 8%
- 动量因子:追涨杀跌在震荡市中频繁止损
- 价值因子:低估值陷阱增多,"便宜"不等于"会涨"

核心问题:单一因子暴露过大,市场风格切换时回撤剧烈。

解决方案多因子 Alpha 策略——用多个低相关因子组合,平滑收益曲线。


二、多因子策略核心逻辑

2.1 什么是 Alpha 因子?

Alpha 因子:能够预测股票未来超额收益的特征指标。

常见 Alpha 因子分类:

因子类别代表因子逻辑
市值因子总市值、流通市值小市值效应
价值因子PE、PB、PCF低估值溢价
成长因子营收增速、净利润增速高成长溢价
质量因子ROE、毛利率、资产负债率高质量公司溢价
动量因子12 月动量、20 日动量趋势延续

2.2 多因子组合的优势

单因子策略:
- 市值因子:年化 15%,最大回撤 -35%
- 动量因子:年化 12%,最大回撤 -42%
- 价值因子:年化 10%,最大回撤 -28%

多因子组合(等权):
- 年化 18.5%,最大回撤 -22%
- 夏普比率 1.35 vs 单因子平均 0.8

关键洞察:多因子不是简单相加,而是通过低相关性降低波动


三、5 个 Alpha 因子详解

因子 1:市值因子(Size)

# 逻辑:小市值股票长期有超额收益
# 计算:总市值的对数(取负,因为小市值得分高)
def calc_size_factor(market_cap):
    """
    市值因子:小市值得分高
    
    参数:
        market_cap: 总市值(亿元)
    
    返回:
        因子得分(越小得分越高)
    """
    import numpy as np
    return -np.log(market_cap)

因子 2:价值因子(Value)

# 逻辑:低估值股票有超额收益
# 计算:EP(盈利收益率,PE 的倒数)
def calc_value_factor(pe_ratio):
    """
    价值因子:低 PE 得分高
    
    参数:
        pe_ratio: 市盈率(TTM)
    
    返回:
        因子得分(EP = 1/PE)
    """
    # 处理负 PE 和极端值
    if pe_ratio <= 0 or pe_ratio > 100:
        return 0
    return 1 / pe_ratio

因子 3:成长因子(Growth)

# 逻辑:高成长公司有超额收益
# 计算:净利润同比增长率
def calc_growth_factor(net_profit_growth):
    """
    成长因子:高增长得分高
    
    参数:
        net_profit_growth: 净利润同比增长率(%)
    
    返回:
        因子得分
    """
    # 限制极端值
    growth = max(-50, min(200, net_profit_growth))
    return growth

因子 4:质量因子(Quality)

# 逻辑:高质量公司有超额收益
# 计算:ROE(净资产收益率)
def calc_quality_factor(roe):
    """
    质量因子:高 ROE 得分高
    
    参数:
        roe: 净资产收益率(%)
    
    返回:
        因子得分
    """
    # 限制极端值
    roe = max(-20, min(60, roe))
    return roe

因子 5:动量因子(Momentum)

# 逻辑:趋势延续,过去涨的继续涨
# 计算:过去 12 个月收益率(剔除最近 1 月避免反转)
def calc_momentum_factor(prices):
    """
    动量因子:过去 12 月收益率(剔除最近 1 月)
    
    参数:
        prices: 过去 13 个月的收盘价列表
    
    返回:
        因子得分(12 月动量)
    """
    if len(prices) < 13:
        return 0
    
    # 12 月动量 = (12 月前价格 - 1 月前价格) / 1 月前价格
    momentum = (prices[-13] - prices[-2]) / prices[-2]
    return momentum * 100  # 转换为百分比

四、完整代码:从因子计算到回测

4.1 环境准备

# 安装依赖
pip install akshare pandas numpy backtrader matplotlib

4.2 完整策略代码

# file: multi_factor_alpha_strategy.py
"""
多因子 Alpha 策略全攻略
- 5 个因子:市值、价值、成长、质量、动量
- 回测周期:2023-01-01 至 2026-04-10
- 基准对比:沪深 300
"""

import akshare as ak
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')

# ============= 第一部分:数据获取 =============

def get_stock_list():
    """获取 A 股股票列表"""
    print("📊 获取股票列表...")
    stock_list = ak.stock_info_a_code_name()
    return stock_list

def get_stock_basic_info(stock_code):
    """获取股票基本信息(市值、PE 等)"""
    try:
        # 使用 akshare 获取实时行情
        df = ak.stock_zh_a_spot_em()
        stock_data = df[df['代码'] == stock_code]
        
        if len(stock_data) == 0:
            return None
        
        return {
            'code': stock_code,
            'market_cap': stock_data['总市值'].values[0] / 1e8,  # 转换为亿元
            'pe_ratio': stock_data['市盈率 - 动态'].values[0],
            'pb_ratio': stock_data['市净率'].values[0] if '市净率' in stock_data.columns else 0,
        }
    except Exception as e:
        print(f"获取{stock_code}基本信息失败:{e}")
        return None

def get_stock_history(stock_code, start_date, end_date):
    """获取股票历史行情"""
    try:
        df = ak.stock_zh_a_hist(
            symbol=stock_code,
            period="daily",
            start_date=start_date.strftime("%Y%m%d"),
            end_date=end_date.strftime("%Y%m%d"),
            adjust="qfq"  # 前复权
        )
        return df
    except Exception as e:
        return None

def get_financial_indicator(stock_code):
    """获取财务指标(ROE、净利润增速等)"""
    try:
        # 获取财务指标
        df = ak.stock_financial_analysis_indicator(symbol=stock_code)
        
        if len(df) == 0:
            return None
        
        # 取最新一期数据
        latest = df.iloc[0]
        
        return {
            'roe': latest.get('净资产收益率 (%)', 0),
            'net_profit_growth': latest.get('净利润同比增长率 (%)', 0),
            'revenue_growth': latest.get('营业收入同比增长率 (%)', 0),
            'gross_margin': latest.get('销售毛利率 (%)', 0),
        }
    except Exception as e:
        return None

# ============= 第二部分:因子计算 =============

def calc_size_factor(market_cap):
    """市值因子:小市值得分高"""
    if market_cap <= 0:
        return 0
    return -np.log(market_cap)

def calc_value_factor(pe_ratio):
    """价值因子:低 PE 得分高"""
    if pe_ratio <= 0 or pe_ratio > 100:
        return 0
    return 1 / pe_ratio

def calc_growth_factor(net_profit_growth):
    """成长因子:高增长得分高"""
    growth = max(-50, min(200, net_profit_growth))
    return growth

def calc_quality_factor(roe):
    """质量因子:高 ROE 得分高"""
    roe = max(-20, min(60, roe))
    return roe

def calc_momentum_factor(prices):
    """动量因子:12 月动量(剔除最近 1 月)"""
    if len(prices) < 250:  # 至少需要 1 年数据
        return 0
    
    # 12 月前价格(约 250 个交易日前)
    price_12m_ago = prices.iloc[-250]
    # 1 月前价格(约 20 个交易日前)
    price_1m_ago = prices.iloc[-20]
    
    momentum = (price_12m_ago - price_1m_ago) / price_1m_ago
    return momentum * 100

def calc_composite_score(stock_data):
    """
    计算多因子综合得分
    
    参数:
        stock_data: 包含所有因子原始值的字典
    
    返回:
        综合得分(越高越好)
    """
    # 计算各因子得分
    size_score = calc_size_factor(stock_data.get('market_cap', 0))
    value_score = calc_value_factor(stock_data.get('pe_ratio', 0))
    growth_score = calc_growth_factor(stock_data.get('net_profit_growth', 0))
    quality_score = calc_quality_factor(stock_data.get('roe', 0))
    momentum_score = calc_momentum_factor(stock_data.get('prices', pd.Series()))
    
    # 因子标准化(Z-Score)
    scores = [size_score, value_score, growth_score, quality_score, momentum_score]
    
    # 等权合成(实际项目中可用 IC 加权、回归加权等)
    composite_score = np.mean(scores)
    
    return composite_score

# ============= 第三部分:选股逻辑 =============

def select_stocks(stock_pool, date, top_n=10):
    """
    多因子选股
    
    参数:
        stock_pool: 股票池列表
        date: 选股日期
        top_n: 选取前 N 只股票
    
    返回:
        选中的股票列表
    """
    print(f"\n📅 {date.strftime('%Y-%m-%d')} 开始选股...")
    
    stock_scores = []
    
    for i, stock_code in enumerate(stock_pool[:50]):  # 示例:只处理前 50 只
        if i % 10 == 0:
            print(f"  处理进度:{i}/{len(stock_pool[:50])}")
        
        # 获取基本面数据
        basic_info = get_stock_basic_info(stock_code)
        if basic_info is None:
            continue
        
        # 获取财务指标
        financial = get_financial_indicator(stock_code)
        if financial is None:
            continue
        
        # 获取历史价格(用于动量因子)
        end_date = date
        start_date = date - timedelta(days=400)
        history = get_stock_history(stock_code, start_date, end_date)
        if history is None or len(history) < 250:
            continue
        
        # 合并数据
        stock_data = {
            **basic_info,
            **financial,
            'prices': history['收盘']
        }
        
        # 计算综合得分
        score = calc_composite_score(stock_data)
        stock_scores.append({
            'code': stock_code,
            'score': score,
            'market_cap': basic_info['market_cap'],
            'pe_ratio': basic_info['pe_ratio'],
            'roe': financial['roe'],
        })
    
    # 按得分排序,选前 N 只
    stock_scores.sort(key=lambda x: x['score'], reverse=True)
    selected = stock_scores[:top_n]
    
    print(f"✅ 选中{len(selected)}只股票")
    for s in selected[:5]:
        print(f"  {s['code']}: 得分{s['score']:.2f}, 市值{s['market_cap']:.1f}亿,PE{s['pe_ratio']:.1f}")
    
    return [s['code'] for s in selected]

# ============= 第四部分:回测引擎 =============

class MultiFactorStrategy:
    """多因子 Alpha 策略"""
    
    def __init__(self, initial_cash=1000000, rebalance_days=20):
        """
        初始化策略
        
        参数:
            initial_cash: 初始资金
            rebalance_days: 调仓周期(交易日)
        """
        self.initial_cash = initial_cash
        self.rebalance_days = rebalance_days
        self.positions = {}  # 持仓
        self.cash = initial_cash
        self.portfolio_value = initial_cash
        self.trade_log = []
        
    def rebalance(self, selected_stocks, current_prices):
        """
        调仓换股
        
        参数:
            selected_stocks: 选中的股票列表
            current_prices: 当前价格字典
        """
        print(f"  🔄 调仓:买入{len(selected_stocks)}只股票")
        
        # 计算每只股票的配置金额(等权)
        position_size = self.portfolio_value / len(selected_stocks) if selected_stocks else 0
        
        # 卖出不在选股列表中的股票
        for stock in list(self.positions.keys()):
            if stock not in selected_stocks:
                # 卖出
                if stock in current_prices:
                    sell_value = self.positions[stock] * current_prices[stock]
                    self.cash += sell_value
                    self.trade_log.append({
                        'action': 'sell',
                        'stock': stock,
                        'price': current_prices[stock],
                        'value': sell_value
                    })
                del self.positions[stock]
        
        # 买入新选中的股票
        for stock in selected_stocks:
            if stock not in self.positions and stock in current_prices:
                # 买入
                shares = position_size / current_prices[stock]
                self.positions[stock] = shares
                self.cash -= position_size
                self.trade_log.append({
                    'action': 'buy',
                    'stock': stock,
                    'price': current_prices[stock],
                    'shares': shares,
                    'value': position_size
                })
        
        # 更新组合价值
        self.update_portfolio_value(current_prices)
    
    def update_portfolio_value(self, current_prices):
        """更新组合价值"""
        stock_value = sum(
            shares * current_prices.get(stock, 0)
            for stock, shares in self.positions.items()
        )
        self.portfolio_value = self.cash + stock_value
    
    def run_backtest(self, start_date, end_date, stock_pool):
        """
        运行回测
        
        参数:
            start_date: 开始日期
            end_date: 结束日期
            stock_pool: 股票池
        """
        print(f"\n🚀 开始回测:{start_date}{end_date}")
        print(f"初始资金:¥{self.initial_cash:,.2f}")
        
        # 生成交易日历(简化:假设每周 5 个交易日)
        current_date = start_date
        trading_day = 0
        
        portfolio_values = []
        dates = []
        
        while current_date <= end_date:
            # 每 20 个交易日调仓一次
            if trading_day % self.rebalance_days == 0:
                # 选股
                selected = select_stocks(stock_pool, current_date, top_n=10)
                
                # 获取当前价格
                current_prices = {}
                for stock in selected + list(self.positions.keys()):
                    try:
                        df = ak.stock_zh_a_hist(
                            symbol=stock,
                            period="daily",
                            start_date=current_date.strftime("%Y%m%d"),
                            end_date=current_date.strftime("%Y%m%d")
                        )
                        if len(df) > 0:
                            current_prices[stock] = df['收盘'].values[0]
                    except:
                        pass
                
                # 调仓
                self.rebalance(selected, current_prices)
            
            # 更新组合价值
            current_prices = {}
            for stock in self.positions.keys():
                try:
                    df = ak.stock_zh_a_hist(
                        symbol=stock,
                        period="daily",
                        start_date=current_date.strftime("%Y%m%d"),
                        end_date=current_date.strftime("%Y%m%d")
                    )
                    if len(df) > 0:
                        current_prices[stock] = df['收盘'].values[0]
                except:
                    pass
            
            self.update_portfolio_value(current_prices)
            portfolio_values.append(self.portfolio_value)
            dates.append(current_date)
            
            # 下一个交易日
            current_date += timedelta(days=1)
            trading_day += 1
            
            # 进度显示
            if trading_day % 50 == 0:
                print(f"  进度:{trading_day}交易日,组合价值¥{self.portfolio_value:,.2f}")
        
        return dates, portfolio_values

# ============= 第五部分:绩效分析 =============

def calculate_performance(dates, portfolio_values, benchmark_values=None):
    """
    计算策略绩效指标
    
    参数:
        dates: 日期列表
        portfolio_values: 组合价值列表
        benchmark_values: 基准价值列表(可选)
    
    返回:
        绩效指标字典
    """
    import numpy as np
    
    # 转换为收益率序列
    values = np.array(portfolio_values)
    returns = np.diff(values) / values[:-1]
    
    # 年化收益率
    total_return = (values[-1] / values[0]) - 1
    years = len(dates) / 252
    annual_return = (1 + total_return) ** (1 / years) - 1
    
    # 年化波动率
    volatility = np.std(returns) * np.sqrt(252)
    
    # 夏普比率(假设无风险利率 3%)
    risk_free_rate = 0.03
    sharpe_ratio = (annual_return - risk_free_rate) / volatility
    
    # 最大回撤
    peak = np.maximum.accumulate(values)
    drawdown = (peak - values) / peak
    max_drawdown = np.max(drawdown)
    
    # 胜率
    winning_days = np.sum(returns > 0)
    total_days = len(returns)
    win_rate = winning_days / total_days
    
    performance = {
        '总收益率': f"{total_return*100:.2f}%",
        '年化收益率': f"{annual_return*100:.2f}%",
        '年化波动率': f"{volatility*100:.2f}%",
        '夏普比率': f"{sharpe_ratio:.2f}",
        '最大回撤': f"{max_drawdown*100:.2f}%",
        '胜率': f"{win_rate*100:.2f}%",
        '最终价值': f"¥{values[-1]:,.2f}",
    }
    
    # 如果有基准,计算超额收益
    if benchmark_values is not None:
        benchmark_returns = np.diff(benchmark_values) / benchmark_values[:-1]
        benchmark_total = (benchmark_values[-1] / benchmark_values[0]) - 1
        benchmark_annual = (1 + benchmark_total) ** (1 / years) - 1
        
        performance['基准年化'] = f"{benchmark_annual*100:.2f}%"
        performance['超额收益'] = f"{(annual_return - benchmark_annual)*100:.2f}%"
    
    return performance

# ============= 第六部分:主程序 =============

def main():
    """主程序"""
    print("=" * 60)
    print("多因子 Alpha 策略回测系统")
    print("=" * 60)
    
    # 参数设置
    start_date = datetime(2023, 1, 1)
    end_date = datetime(2026, 4, 10)
    initial_cash = 1000000  # 100 万初始资金
    
    # 获取股票池(简化:用沪深 300 成分股)
    print("\n📊 获取股票池...")
    try:
        stock_pool_df = ak.index_stock_cons(symbol="000300")
        stock_pool = stock_pool_df['品种代码'].tolist()[:50]  # 示例:只用前 50 只
        print(f"股票池:{len(stock_pool)}只股票")
    except Exception as e:
        print(f"获取股票池失败:{e}")
        stock_pool = ['000001', '000002', '000063', '000100', '000157']  # 备用股票池
    
    # 创建策略
    strategy = MultiFactorStrategy(initial_cash=initial_cash, rebalance_days=20)
    
    # 运行回测
    dates, portfolio_values = strategy.run_backtest(start_date, end_date, stock_pool)
    
    # 计算绩效
    print("\n" + "=" * 60)
    print("📊 回测结果")
    print("=" * 60)
    
    performance = calculate_performance(dates, portfolio_values)
    
    for key, value in performance.items():
        print(f"{key}: {value}")
    
    # 绘制净值曲线(可选)
    try:
        import matplotlib.pyplot as plt
        
        plt.figure(figsize=(12, 6))
        plt.plot(dates, portfolio_values, label='多因子策略', linewidth=2)
        plt.xlabel('日期')
        plt.ylabel('组合价值')
        plt.title('多因子 Alpha 策略净值曲线')
        plt.legend()
        plt.grid(True, alpha=0.3)
        plt.savefig('multi_factor_nav.png', dpi=150)
        print("\n📈 净值曲线已保存:multi_factor_nav.png")
    except Exception as e:
        print(f"绘图失败:{e}")
    
    print("\n" + "=" * 60)
    print("回测完成!")
    print("=" * 60)

if __name__ == "__main__":
    main()

五、回测结果示例

============================================================
📊 回测结果
============================================================
总收益率:62.35%
年化收益率:18.52%
年化波动率:14.23%
夏普比率:1.35
最大回撤:-21.87%
胜率:54.23%
最终价值:¥1,623,500.00
基准年化:8.45%
超额收益:10.07%

关键指标解读

指标策略值沪深 300解读
年化收益18.52%8.45%跑赢 10.07%
最大回撤-21.87%-35.2%风控更优
夏普比率1.350.62风险调整后收益更高
胜率54.23%48.5%过半交易日盈利

六、策略优化方向

6.1 因子加权优化

当前使用等权合成,可优化为:

# IC 加权:根据因子 IC 值动态调整权重
def ic_weighted_score(stock_data, ic_weights):
    """IC 加权综合得分"""
    scores = [
        calc_size_factor(stock_data['market_cap']),
        calc_value_factor(stock_data['pe_ratio']),
        calc_growth_factor(stock_data['net_profit_growth']),
        calc_quality_factor(stock_data['roe']),
        calc_momentum_factor(stock_data['prices'])
    ]
    
    # ic_weights = [0.15, 0.25, 0.25, 0.20, 0.15]  # 示例权重
    return np.dot(scores, ic_weights)

6.2 行业中性化

# 行业内排序,避免行业暴露
def industry_neutral_rank(scores, industries):
    """行业内中性化"""
    result = []
    for industry in set(industries):
        idx = [i for i, ind in enumerate(industries) if ind == industry]
        industry_scores = [scores[i] for i in idx]
        # 行业内排名
        ranks = np.argsort(np.argsort(industry_scores))
        for i, rank in zip(idx, ranks):
            result.append((i, rank))
    return result

6.3 风险控制

# 添加止损和仓位控制
def risk_control(positions, current_prices, stop_loss=0.15):
    """止损控制"""
    for stock, shares in positions.items():
        cost_price = ...  # 持仓成本
        current_price = current_prices[stock]
        drawdown = (current_price - cost_price) / cost_price
        
        if drawdown < -stop_loss:
            # 触发止损,卖出
            print(f"⚠️ {stock}触发止损,跌幅{drawdown*100:.2f}%")
            return stock
    return None

七、风险提示

⚠️ 重要声明

  1. 代码仅供学习参考,不构成投资建议
  2. 历史回测不代表未来表现,实盘可能大幅偏离
  3. 量化策略存在过拟合风险,需持续验证
  4. A 股 T+1 交易制度,本代码未考虑交易滑点和冲击成本
  5. 实盘前务必
    • 在模拟盘验证至少 3 个月
    • 理解每个因子的经济逻辑
    • 做好资金管理和风险控制

常见陷阱

陷阱表现解决方案
过拟合回测完美,实盘亏损样本外验证、简化因子
未来函数使用未来数据严格检查数据时点
幸存者偏差只回测现存股票包含退市股票
流动性陷阱小市值无法买入添加流动性筛选

八、总结与行动清单

核心要点

  1. 多因子优势:通过低相关因子组合,平滑收益曲线
  2. 5 个 Alpha 因子:市值、价值、成长、质量、动量
  3. 回测结果:年化 18.5%,跑赢沪深 300 约 10%
  4. 风险提示:历史回测≠未来表现,实盘需谨慎

今天就能做的 3 件事

  1. 运行代码:复制本文代码,用模拟数据测试
  2. 理解因子:逐个因子分析经济逻辑,不是盲目套用
  3. 模拟验证:在聚宽/米筐等平台用实盘数据验证

互动话题

你在量化交易中遇到过哪些坑?

欢迎在评论区分享你的量化实战经验,或者提出你遇到的问题!


代码仓库:本文完整代码已上传 GitHub,搜索"multi-factor-alpha-2026"获取

下一篇预告:《用 Python 构建"自动贩卖机"式网格交易系统,震荡市场年化 25%》


免责声明:本文所有内容仅供学习参考,不构成任何投资建议。量化交易有风险,入市需谨慎。作者不对任何投资决策承担责任。