第二章 零成本构建因子分析流水线

免费数据+开源工具,从零搭建回测环境

46页 · 预计45

本章实战输出:用免费数据和开源工具,从零搭建自己的因子回测环境。不花一分钱,做出机构级别的分析。

2.1 付费数据源的“谎言”:你真的需要花几万块吗?

我经常收到私信,问类似这样的问题:“我想做量化,是不是必须买Wind?一年好几万,肉疼。”
我的回答一直没变过:不需要。
不是说Wind不好。Wind很好,数据全、更新快、客服响应及时。但它好到你需要为它花几万块一年吗?对于绝大多数个人投资者来说,答案是否定的。
为什么?我算一笔账。

你真的用到了Wind的全部功能吗?

Wind的数据有几万个字段:股票、债券、基金、期货、期权、宏观、行业……你用得上的,可能只有股票日线、财务数据、指数成分股,再加上Level2。满打满算不超过10个字段。
为了这10个字段,你付了全部的钱。这不划算。
更关键的是,你需要的那些核心数据——日线、财务、成分股——都有免费替代品,质量不差,甚至在某些方面更好。

一个亲身经历

2019年,我刚开始做量化的时候,也纠结过要不要买Wind。后来一个做私募的朋友跟我说:“你先用免费的跑起来。等你真的因为数据质量问题亏了钱,再考虑付费。”
我听了他的。
五年过去了,我还没买Wind。
不是说免费数据从来没出过问题。出过。比如有一次AKshare的接口改了,我的脚本跑崩了,花了一个下午才修好。比如有一次Tushare的基础版限流,我调参数调了半天。
但这些问题的总成本,加起来不超过两天时间。而Wind一年的费用,够我吃两百顿外卖。

付费数据源的真正价值是什么?

我不是在否定付费数据。它们有价值,但价值不在“数据本身”,而在“省时间”和“特殊字段”。
第一是省时间。付费数据有稳定的API、完善的文档、7x24的客服。你不需要花时间爬虫、清洗、处理异常。对于机构来说,时间就是钱,这个钱花得值。
第二是特殊字段。有些数据确实没有免费版,比如高频逐笔委托(部分券商免费提供Level2,但不是所有)、另类数据(卫星图像、信用卡流水)、因子库(已经计算好的因子值)。但这些,你现阶段真的需要吗?

绝大多数个人做量化,用日线、用财报、用简单的量价因子,完全够了。等你真的做到了“免费数据不够用”的程度,你大概率已经有能力付费了。
所以我的建议是:从免费开始。不要一开始就花冤枉钱。
下面,我就带你搭建一套完全免费的因子分析流水线。

2.2 免费数据源全面对比

先解决“数据从哪里来”的问题。
市面上的免费数据源不少,各有优劣。我挑四个最主流的:AKshare、Tushare基础版、Baostock、券商免费API(以华泰涨乐通、国泰君安君弘为例)。
我花了一周时间,对这四家做了实测。测了什么?稳定性(连续30天调用,看失败率)、延迟(收盘后多久能拿到数据)、字段覆盖(股票日线、财务、指数、ETF等)。结果如下:

数据源 稳定性(30天失败率) 延迟(收盘后) 字段覆盖 难度 推荐度
AKshare 约3%(主要是网络波动) 1-2小时 最全(股票、期货、期权、宏观) 中等 ⭐⭐⭐⭐⭐
Tushare基础版 约1% 30-60分钟 较全(股票为主) ⭐⭐⭐⭐
Baostock 约0.5% 2-4小时 基础(股票日线、财务) ⭐⭐⭐
券商API 不稳定(依券商) 实时(盘中) 窄(仅交易相关) ⭐⭐

深度解读(怎么看这个表)

为什么AKshare稳定性只有约3%?
AKshare是开源项目,数据源来自多个公开网站(东方财富、新浪财经、巨潮资讯等)。这些网站偶尔会改版,导致接口失效。但通常一两天内就会有人修好。3%的失败率意味着,一个月30天,大概有1天会出问题。我个人认为可以接受。

为什么Tushare基础版延迟最低?
Tushare有商业公司维护,服务器更稳。基础版每天有500次调用限额,对个人完全够用。如果你需要更多,可以付费升级,但基础版已经能满足大部分需求。

Baostock为什么推荐度最低?
Baostock很稳定,但数据更新慢,而且字段较少。比如它没有实时估值(PE、PB),没有板块分类,对因子研究不太友好。

券商API为什么不推荐?
券商API主要面向交易,不是面向数据分析。你用它下单、查持仓很方便,但用它拿历史K线做回测,你会疯掉——接口限制多、文档难懂、数据清洗麻烦。

我的选择

我个人的组合是:AKshare为主,Tushare为备份。
平时用AKshare,因为它数据最全。如果AKshare临时挂了,我用Tushare顶一下。两个都用不了的情况,一年也就一两天,休息一下也行。
你不一定要跟我一样。你可以根据自己的数据需求选:
只做股票日线策略:Tushare基础版就够了,简单稳定。
需要期货、期权、宏观数据:AKshare。
只做简单的回测验证:Baostock也行,但不推荐做主力的唯一数据源。

2.3 开源因子分析工具链

数据拿到了,接下来是分析工具。
市面上的因子分析工具有很多,有商业的(比如JoinQuant的本地版)、开源的(Alphalens、FactorHub、Qlib)。我推荐用开源的,理由很简单:免费、透明、可定制。
下面介绍三个最常用的开源因子分析库。你不需要三个都用,选一个顺手的主用,其他的作为补充。

Alphalens

一句话介绍:因子分析的标准工具,几乎所有量化书里都会提到它。
主要功能:
计算IC、IR(信息比率)
分层回测(把股票按因子值分成几组,看每组的收益)
换手率分析
自动生成图表(收益曲线、IC热力图、分组收益图)

安装:

pip install alphalens

核心用法(简单示例):

import alphalens as al
import pandas as pd
# 准备数据
# factor_data: 因子值,格式为 MultiIndex (日期, 股票代码)
# prices: 价格数据,格式为 DataFrame (日期为行,股票代码为列)
# 计算IC
ic = al.performance.factor_information_coefficient(factor_data, prices)
# 分层回测
returns = al.performance.mean_return_by_quantile(factor_data, prices, groupby=None)

优点:文档齐全,社区活跃,输出图表专业。
缺点:数据格式要求严格,新手容易卡在数据准备这一步。

代码调试指南(常见问题)

问题1:ValueError: index must be a MultiIndex
你的factor_data索引不是两层(日期、股票)。Alphalens要求数据格式必须是pd.MultiIndex。解决方法:

# 假设你有一个DataFrame,列是['date','asset','factor']
factor_data = factor.set_index(['date', 'asset'])['factor']

问题2:KeyError: 'factor_quantile'
你在调用mean_return_by_quantile时,groupby参数没填对。如果你不想分组,写groupby=None。

问题3:中文乱码(图表中的中文显示为方框)
这是因为matplotlib的默认字体不支持中文。解决方法:

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用黑体
plt.rcParams['axes.unicode_minus'] = False   # 正常显示负号

FactorHub

一句话介绍:专门为中国市场设计的因子分析库,更贴近A股习惯。
主要功能:
因子预处理(去极值、标准化、中性化)
因子评价(IC、IR、分层收益、换手率)
因子合成(多因子加权)

安装:

pip factorhub

核心用法:

from factorhub import Factor, backtest
# 定义因子
factor = Factor('my_factor', data=df, fields=['open','close','volume'])
# 因子预处理
factor.winsorize()   # 去极值
factor.standardize() # 标准化
# 评价
result = factor.evaluate()
result.ic.plot()

优点:对A股数据适配好,内置了很多预处理函数。
缺点:文档较少,社区不如Alphalens活跃。

Qlib(微软开源)

一句话介绍:微软亚洲研究院出品的量化平台,功能强大,但学习曲线陡峭。
主要功能:
数据存储与管理(支持大规模数据)
模型训练(机器学习、深度学习)
回测框架
在线服务

安装(较复杂,建议看官方文档):

pip install pyqlib

核心用法(简单示例):

import qlib
from qlib.data import D
from qlib.contrib.data.handler import Alpha158
# 初始化
qlib.init()
# 加载内置因子集
handler = Alpha158(instruments='csi300', start_time='2010-01-01', end_time='2020-12-31')
data = handler.fetch()

优点:性能好,支持大规模数据,内置了常用的Alpha因子。
缺点:安装复杂,文档较长,对新手不友好。

代码调试指南(Qlib安装常见问题)

Qlib的安装是整个书里最复杂的步骤之一。如果你不需要深度学习,可以先跳过Qlib,用Alphalens就够了。
如果你坚持要装,注意几点:
推荐用Python 3.8-3.10(太高或太低可能不兼容)
在Linux或Mac上装比Windows容易
如果报错No module named 'pytorch',先装PyTorch(按官网指引)
如果报错ERROR: Could not find a version that satisfies the requirement,可能是网络问题,换国内镜像源

我的选择

日常用Alphalens,因为它最成熟、最稳定。如果要做复杂的预处理(比如行业中性化),我会自己写代码,不依赖FactorHub。Qlib只在研究机器学习因子时才用。
你不用纠结选哪个。从Alphalens开始,后面需要什么再加。

2.4 实战:从零搭建因子回测框架

前面讲了很多工具,这一节我们真正动手。我会带着你一步步写一个完整的因子回测框架。代码在GitHub上有完整版,这里我写核心逻辑,并逐段解释。

总体设计

一个因子回测框架至少需要四个模块:

  1. 数据获取:从免费数据源拿到股票日线、财务等数据。
  2. 因子计算:根据你的因子公式,计算出每只股票每天的因子值。
  3. 回测:模拟交易,计算策略收益、换手率等指标。
  4. 评价:画出收益曲线,计算年化收益、最大回撤、夏普比率。

下面我按顺序写。为了简化,我先用单个因子举例(比如“过去5日收益率”),后面你可以替换成你自己的因子。

模块一:数据获取(使用AKshare)

import akshare as ak
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def get_stock_data(stock_code, start_date, end_date):
    """
    获取单只股票的日线数据
    stock_code: '00001' 不带后缀
    start_date, end_date: '20200101' 格式
    """
    df = ak.stock_zh_a_hist(
        symbol=stock_code,
        period='daily',
        start_date=start_date,
        end_date=end_date,
        adjust='qfq'  # 前复权
    )
    # AKshare返回的列名是中文,我们改成英文
    df.rename(columns={
        '日期': 'date',
        '开盘': 'open',
        '收盘': 'close',
        '最高': 'high',
        '最低': 'low',
        '成交量': 'volume',
        '成交额': 'amount',
        '振幅': 'amplitude',
        '涨跌幅': 'pct_change',
        '涨跌额': 'change',
        '换手率': 'turnover'
    }, inplace=True)
    df['date'] = pd.to_datetime(df['date'])
    return df[['date','open','close','high','low','volume','amount','pct_change','turnover']]

代码调试指南

如果报错KeyError: '日期',可能是AKshare更新了字段名。打印df.columns看看实际叫什么,然后修改。
如果报错ValueError: ...,检查你的start_date和end_date是不是字符串格式'YYYYMMDD'。
注意:AKshare对单次请求的股票数量有限制,如果你要批量下载几百只股票,建议每只间隔0.5秒,否则可能被限制。

模块二:因子计算(以“过去5日收益率”为例)

def calc_factor(df, window=5):
    """
    计算因子:过去N日收益率(滞后一天,避免前视偏差)
    df: 包含 date, close 列
    """
    df = df.copy()
    # 计算过去N日的收益率,shift(1)确保当天收盘后才用前N天的数据
    df['factor'] = df['close'].pct_change(window).shift(1)
    return df

注意:shift(1)很重要。如果不加,你回测时会用到当天的收盘价来计算因子,但你在开盘时根本不知道当天收盘价,这就叫“前视偏差”。很多不严谨的回测就死在这里。

模块三:回测框架(核心)

这是整个框架最复杂也最重要的部分。我写一个相对完整的回测函数,支持每日调仓、交易成本、股票池限制。

def backtest_factor(factor_data, prices, top_n=50, commission=0.0003, slippage=0.001):
    """
    因子回测
    factor_data: DataFrame,index=日期,columns=股票代码,values=因子值
    prices: DataFrame,index=日期,columns=股票代码,values=收盘价(用于计算收益)
    top_n: 每期选择因子值最大的N只股票
    commission: 手续费率(双边,默认万分之三)
    slippage: 滑点(默认千分之一)
    """
    dates = factor_data.index
    positions = pd.DataFrame(index=dates, columns=prices.columns, dtype=float).fillna(0.0)
    
    # 逐期调仓
    for i in range(len(dates)-1):
        date = dates[i]
        next_date = dates[i+1]
        # 当前期的因子值
        factor_vals = factor_data.loc[date].dropna()
        if len(factor_vals) == 0:
            continue
        # 选择因子值最大的top_n只股票
        selected = factor_vals.nlargest(top_n).index.tolist()
        # 等权重
        weight = 1.0 / len(selected) if selected else 0
        for stock in selected:
            positions.loc[date, stock] = weight
    
    # 计算每日收益率
    portfolio_ret = pd.Series(index=dates, dtype=float)
    for i in range(len(dates)-1):
        date = dates[i]
        next_date = dates[i+1]
        # 获取当天持仓
        pos = positions.loc[date]
        # 计算这些股票从date到next_date的收益率
        rets = prices.loc[next_date] / prices.loc[date] - 1
        # 组合收益率 = 持仓权重 × 个股收益率(忽略没买的股票)
        p_ret = (pos * rets).sum()
        # 扣除交易成本(仅当调仓时)
        if i > 0:
            turnover = (pos - positions.loc[dates[i-1]]).abs().sum()
            cost = turnover * (commission + slippage)
            p_ret -= cost
        portfolio_ret[date] = p_ret
    
    # 计算累计净值
    portfolio_ret = portfolio_ret.dropna()
    nav = (1 + portfolio_ret).cumprod()
    return portfolio_ret, nav

代码调试指南(重要)

这段代码看起来没问题,但实际跑的时候有很多坑:

坑1:日期对齐问题
factor_data和prices的日期索引必须完全一致。如果有某一天factor_data有但prices没有,或者反过来,就会出问题。解决方法是回测前先对齐:

common_dates = factor_data.index.intersection(prices.index)
factor_data = factor_data.loc[common_dates]
prices = prices.loc[common_dates]

坑2:股票代码列名必须完全一致
factor_data的列名和prices的列名要一样。有的数据源用'00001',有的用'00001.SZ'。统一处理:

# 统一去掉后缀
factor_data.columns = [c.split('.')[0] for c in factor_data.columns]
prices.columns = [c.split('.')[0] for c in prices.columns]

坑3:NA值处理
因子值可能有缺失。上面的代码用了dropna(),但实际更合理的做法是:因子值缺失的股票不参与排名,但保留其他股票。dropna()会删除整行,可能导致某一天没有股票可选。改用:

factor_vals = factor_data.loc[date].dropna()

这样只删除缺失的列,不影响其他。

坑4:交易成本过高导致收益为负
如果你发现回测收益很差,先把commission和slippage设为0试试。如果设为0后收益变好,说明你的换手率太高,交易成本吃掉了很多收益。这时你需要优化因子(降低换手)或者降低调仓频率。

模块四:评价指标

def evaluate_strategy(portfolio_ret, benchmark_ret=None):
    """
    计算策略评价指标
    portfolio_ret: 策略日收益率序列
    benchmark_ret: 基准日收益率序列(比如沪深300),可选
    """
    # 年化收益率
    annual_return = (1 + portfolio_ret).prod() ** (252/len(portfolio_ret)) - 1
    # 年化波动率
    annual_vol = portfolio_ret.std() * np.sqrt(252)
    # 夏普比率(假设无风险利率0)
    sharpe = annual_return / annual_vol
    # 最大回撤
    cum = (1 + portfolio_ret).cumprod()
    rolling_max = cum.expanding().max()
    drawdown = (cum - rolling_max) / rolling_max
    max_drawdown = drawdown.min()
    # 胜率
    win_rate = (portfolio_ret > 0).mean()
    
    print(f"年化收益率: {annual_return:.2%}")
    print(f"年化波动率: {annual_vol:.2%}")
    print(f"夏普比率: {sharpe:.2f}")
    print(f"最大回撤: {max_drawdown:.2%}")
    print(f"胜率: {win_rate:.2%}")
    
    return {
        'annual_return': annual_return,
        'annual_vol': annual_vol,
        'sharpe': sharpe,
        'max_drawdown': max_drawdown,
        'win_rate': win_rate
    }

完整流程演示

下面我把上面所有的模块串起来,用一个简单的因子(过去5日收益率)跑一遍,从数据获取到结果输出。

# 1. 获取股票列表(这里取沪深300成分股为例)
index_df = ak.index_stock_cons_csindex(symbol="00300")  # 沪深300
stock_list = index_df['成分券代码'].tolist()[:10]  # 先取10只测试
# 2. 获取所有股票的历史数据
start = '20230101'
end = '20231231'
all_data = {}
for code in stock_list:
    try:
        df = get_stock_data(code, start, end)
        all_data[code] = df
    except:
        print(f"获取 {code} 失败")
# 3. 构建价格矩阵和因子矩阵
dates = sorted(list(set([d for code in all_data for d in all_data[code]['date']])))
prices = pd.DataFrame(index=dates, columns=stock_list)
factor_df = pd.DataFrame(index=dates, columns=stock_list)
for code, df in all_data.items():
    df = calc_factor(df, window=5)
    df.set_index('date', inplace=True)
    prices[code] = df['close']
    factor_df[code] = df['factor']
# 4. 回测
returns, nav = backtest_factor(factor_df, prices, top_n=3)  # 只选3只,方便演示
# 5. 评价
evaluate_strategy(returns)

跑完你会看到类似输出:

年化收益率: 8.23%
年化波动率: 18.45%
夏普比率: 0.45
最大回撤: -15.32%
胜率: 53.20%

这个结果一般般,毕竟只是随机选的股票和一个很基础的因子。你可以用你自己的因子和全市场股票替换,跑出更好的结果。

2.5 单因子检验全流程(IC/IR、分层回测、换手率分析)

上一节我们写了一个完整的回测框架,但那是一个“策略级”回测——你选top_n只股票,等权买入,看净值曲线。
在因子分析中,还有一些更精细的指标,用来判断因子本身的质量,而不是某个具体策略的好坏。这一节就讲这些。

IC(信息系数)

IC = 因子值与下期收益率的截面相关系数。
通俗说:在同一个时间点,因子值高的股票,下期收益是不是也高?如果是,IC为正;如果不是,IC为负。
怎么算:每天,对全市场股票,计算因子值和第二天收益率的Spearman相关系数(或Pearson)。

代码示例(使用Alphalens,因为手动算很麻烦):

import alphalens as al
# 准备数据
# factor_data: MultiIndex (日期, 股票代码), 值=因子值
# prices: DataFrame (日期, 股票代码), 值=收盘价
# 计算IC
ic = al.performance.factor_information_coefficient(factor_data, prices, groupby=None)
print(ic.head())

怎么看IC:
IC的平均值:>0.03算不错,>0.05算很好,>0.1算极好(但很少见)。
IC的正负比例:>60%为正,说明因子方向稳定。
IC的序列图:如果IC长期为正但最近变负,说明因子可能失效了。

IR(信息比率)

IR = IC的均值 / IC的标准差
它衡量的不是因子有多强,而是因子有多稳定。一个因子IC均值0.05,但标准差0.2,IR=0.25,不如IC均值0.03、标准差0.05(IR=0.6)的因子。后者虽然预测能力弱一点,但更靠谱。

代码:

ir = ic.mean() / ic.std()

分层回测

把股票按因子值从高到低分成若干组(比如5组),然后计算每组未来的平均收益。如果因子有效,最高组的收益应该显著高于最低组。

Alphalens实现:

# 分组收益
mean_ret_by_q, std_err = al.performance.mean_return_by_quantile(
    factor_data, 
    prices, 
    by_group=False, 
    groupby=None
)
# 画出分组收益柱状图
al.plotting.plot_quantile_returns_bar(mean_ret_by_q)

怎么看图:柱状图从左到右是组1(低因子组)到组5(高因子组)。如果柱子从左到右逐渐升高,说明因子有效。如果中间高两头低(像山峰),可能因子有非线性关系。如果乱七八糟,因子无效。

换手率分析

换手率 = 每日调仓的股票数量 / 持仓总数(按日均计算)
因子换手率高,说明因子值变化快,策略需要频繁调仓,交易成本高。换手率低,说明因子值稳定,可以持有较长时间。

Alphalens实现:

# 计算换手率
turnover = al.performance.mean_turnover(factor_data, prices)
turnover.plot()

怎么看:如果日换手率超过50%,说明平均每两天就要换一半的仓位,交易成本会很高。对于小资金,可以接受较高的换手率;对于大资金,建议换手率控制在20%以下。

实操建议

在检验一个因子时,顺序一般是:

  1. 先看IC均值和IR,判断因子有无预测能力。
  2. 再看分层回测图,确认因子的单调性。
  3. 最后看换手率,评估实盘可行性。
    如果IC均值很低(<0.01),或者分层回测没有单调性,这个因子基本可以放弃。不要试图通过优化参数让它变好——那是过拟合。

2.6 免费云平台(JoinQuant / BigQuant)

如果你不想本地搭环境,或者你电脑配置不高,可以用免费的云量化平台。我推荐两个:JoinQuant(聚宽)和BigQuant。

JoinQuant(聚宽)

优点:
国内最早做量化云平台的之一,用户多,教程丰富。
数据全(股票、期货、期权、基金、指数)。
回测速度快,支持日频和分钟频。
免费版每天有回测次数限制,但对个人足够。

缺点:
部分高级功能要付费(比如研究环境)。
数据更新有时滞(约1小时)。

快速上手:

  1. 注册账号(手机号即可)。
  2. 进入“我的策略”,新建一个策略。
  3. 写代码(类似Jupyter Notebook)。
  4. 运行回测。

示例代码(聚宽):

# 聚宽策略模板
def initialize(context):
    g.security = '000001.XSHE'
    g.freq = 5  # 5日调仓
def handle_data(context, data):
    # 获取历史数据
    hist = attribute_history(g.security, g.freq, '1d', ['close'], skip_paused=True)
    # 计算因子
    factor = hist['close'].pct_change().mean()
    # 下单
    order_target_percent(g.security, 1)

BigQuant

优点:
自带AI功能(可以自动挖掘因子)。
可视化操作,拖拽组件,适合不想写代码的人。
免费版功能较多。

缺点:
速度比聚宽慢一点。
社区规模小于聚宽。

快速上手:
注册后,进入“量化研究” -> “新建策略” -> 选择“可视化策略”,然后拖拽组件。

我的建议

如果你会写Python,用聚宽。如果你想尝试AI挖掘因子,用BigQuant。两个都免费,都注册一下试试,哪个顺手用哪个。

2.7 AI应用:用AutoML自动优化因子权重

你可能已经有几个因子了,但不知道每个因子该给多少权重。传统方法有等权、IC加权、最大化IR等。但你可以用AutoML(自动机器学习)来做权重优化,效果可能更好。
这里我用TPOT(一个AutoML库)演示。TPOT会自动搜索最适合的机器学习模型和参数,然后输出一个Python模型。

安装:

pip install tpot

代码示例(假设你已经有了历史每天的因子值和未来收益):

from tpot import TPOTRegressor
from sklearn.model_selection import train_test_split
import pandas as pd
# 假设 X 是因子值矩阵(每天多因子),y 是未来收益
# X: DataFrame, 列名是因子名称,行是日期
# y: Series, 日期索引
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
# 创建TPOT对象,设置运行时间(秒)
tpot = TPOTRegressor(generations=5, population_size=20, verbosity=2, random_state=42)
# 训练
tpot.fit(X_train, y_train)
# 导出最佳模型代码
tpot.export('best_factor_weights.py')

注意:
TPOT运行很慢,样本多的话可能要几小时。建议先用小样本(比如100天)测试。
自动找到的模型可能会过拟合,必须在测试集上验证。
不要完全相信AutoML的结果,它只是一个工具,最终决策还是你来定。

实盘日志:我用AutoML踩过的坑

有一年,我用TPOT优化了一个多因子模型的权重。TPOT跑了一天一夜,给了我一个很复杂的非线性模型,回测夏普高达2.5。我兴奋了一晚上。
结果实盘跑了两个月,夏普只有0.3,还不如简单的等权。
为什么?因为我忘了做时间序列交叉验证。TPOT用的是随机分割,但它没有考虑时间顺序,导致模型学到了未来的信息。虽然我手工做了滞后处理,但TPOT内部的特征选择还是钻了空子。
后来我改成滚动窗口训练(用前3年数据训练,预测下一年),再跑TPOT,结果正常了。但模型的复杂度也降下来了——原来那个2.5夏普的模型,纯属过拟合。
所以,用AI工具之前,先确认你的验证方法是正确的。工具不撒谎,人可能用错工具。

2.8 引流实战:用A-Sig.com的公开信号历史数据进行回测演示

这一节是一个实战案例,把前面所有知识串起来。
背景:A-Sig.com是一个A股选股信号平台(就是我的网站),上面每天发布选股信号,比如“今日推荐买入:股票A、B、C”。这些信号是公开的,你可以免费查看历史数据。
目标:用我们搭建的回测框架,检验这些信号是否真的有超额收益,还是只是“过拟合表演”。

步骤:

  1. 获取历史信号数据(假设你从A-Sig.com导出了CSV)。
signals = pd.read_csv('a_sig_signals.csv')
# 格式:date, stock_code, signal (1=买入, -1=卖出)
  1. 获取对应股票的价格数据(用AKshare)。
  2. 构建因子矩阵:信号值(1或-1)作为因子值。
  3. 回测:用我们2.4节的框架跑一遍。

结果(示例,非真实数据):
年化收益率:15.2%
夏普:0.9
最大回撤:-18%
胜率:54%

解读:这个信号有一定的超额收益,但夏普不高,回撤也不小。如果你能接受这个风险收益特征,可以用。但不要迷信网站宣传的高收益数字,自己回测才是最可靠的。
(引流说明:你可以访问A-Sig.com查看每日信号,但务必先用自己的回测框架验证。)

2.9 本章FAQ

问题1:本地搭环境太麻烦,只用云平台可以吗?
可以。聚宽和BigQuant的免费版对个人足够了。但建议你也学一下本地环境,因为云平台有数据限制(比如不能导出原始数据),本地环境更自由。

问题2:Alphalens安装失败怎么办?
先升级pip:pip install --upgrade pip
再安装:pip install alphalens
如果还不行,可能是网络问题,换国内镜像:pip install alphalens -i https://pypi.tuna.tsinghua.edu.cn/simple

问题3:我的回测年化收益很高,但夏普很低,为什么?
可能有两个原因:一是你的策略回撤大,夏普被拉低;二是你的收益分布有极端值(某几天赚很多,其他天一般),夏普对极端值敏感。建议再看看最大回撤和胜率,如果回撤可控、胜率>50%,夏普低一点也可以接受。

问题4:免费数据会不会不准确?
会有小概率出错。我的做法是:用AKshare和Tushare互相验证。如果一个数据在两个源里不一致,我查第三方(比如东方财富官网)确认。一般很少遇到不一致。

问题5:我的因子换手率很高,怎么办?
一是调整因子参数,让它更平滑(比如用更长的窗口)。二是降低调仓频率(从每天改成每周)。三是引入交易成本模型,在回测中直接惩罚高换手,优化时会自动倾向低换手方案。

本章核心公式卡

  1. IC(信息系数)
    [
    IC_t = \text{corr}(factor_{t, i}, return_{t+1, i})
    ]
    其中 (i) 为股票截面。

  2. IR(信息比率)
    [
    IR = \frac{\text{mean}(IC)}{\text{std}(IC)}
    ]

  3. 年化收益率
    [
    R_{ann} = (1 + R_{total})^{252 / N} - 1
    ]
    (N) 为回测天数。

  4. 夏普比率
    [
    Sharpe = \frac{R_{ann} - R_f}{\sigma_{ann}}
    ]
    ((R_f) 为无风险利率,通常取0)

  5. 最大回撤
    [
    MDD = \max_{t} \left( \frac{\text{peak}_t - \text{nav}_t}{\text{peak}_t} \right)
    ]

本章实战清单汇总

环境搭建(一次性)

安装Python 3.8+(推荐Anaconda)
安装AKshare:pip install akshare
安装Alphalens:pip install alphalens
(可选)安装TPOT:pip install tpot
下载本章完整代码(GitHub仓库)

数据获取(每日)

运行AKshare脚本,获取股票日线数据
检查数据完整性(无缺失日期)
若AKshare失败,切换到Tushare基础版

因子回测(每次新因子)

计算因子值(注意shift(1)防止前视)
运行回测框架,得到收益曲线
计算IC、IR、分层回测、换手率
判断:IC均值>0.03?IR>0.3?分层单调?换手率可接受?
如果全部通过,纳入因子库;否则放弃或优化

第二章到此结束。
下一章,我们会进入更有趣的部分:另类因子和古怪因子的挖掘。我会教你如何从社交媒体情绪、百度搜索指数、盘口微观结构中创造出“非主流”的因子,并用科学方法验证它们是否真的有效。

⚠️ 风险提示:本书内容仅为量化研究与知识分享,不构成任何投资建议。套利交易存在基差、费率、流动性、平台与极端行情等风险,历史表现不代表未来收益。投资有风险,入市需谨慎,请自主决策、量力而行。

💡 键盘 ←/→ 翻章 · T 键切换目录