我做量化开发8年,用过vnpy、backtrader、QuantConnect不下20个回测框架。最深的感受是:回测系统本身不难,最难的是因子库设计。一个好的因子库能让策略开发效率提升3倍以上。

今天手把手带大家从零构建一套支持AI增强的量化回测系统,重点解决三个问题:如何获取高质量数据、如何高效计算因子、如何用大模型辅助因子挖掘。全文包含可直接运行的Python代码,建议收藏。

一、量化回测系统核心组件

在开始写代码之前,先理解量化回测系统的架构。简单来说,一套完整的回测系统包含四层:

很多新手一上来就纠结用哪个回测框架,其实因子库的设计才是最核心的。我见过太多人用Python字典存因子,数据一多就乱成一锅粥。

二、环境准备与依赖安装

2.1 Python环境要求

建议使用Python 3.10+,需要安装以下核心依赖:

# 基础数据处理
pip install pandas numpy

时间序列数据库(存储因子数据)

pip install polars duckdb

回测框架

pip install backtrader

AI API集成(使用 HolySheep 获取低价大模型)

pip install openai httpx

可视化

pip install matplotlib plotly

2.2 数据目录结构

quantitative_system/
├── data/
│   ├── market/          # 行情数据
│   ├── factors/         # 因子数据
│   └── alternative/      # 另类数据
├── factors/
│   ├── __init__.py
│   ├── base_factor.py   # 因子基类
│   ├── technical.py     # 技术因子
│   ├── fundamental.py   # 基本面因子
│   └── ai_factor.py     # AI生成因子
├── backtest/
│   ├── engine.py
│   └── analyzer.py
├── config.py
└── main.py

三、因子基类设计(核心代码)

我做因子开发的第一原则是:所有因子必须继承统一基类。这样因子管理和组合会非常方便。

# factors/base_factor.py
from abc import ABC, abstractmethod
from datetime import datetime
import polars as pl
from typing import Optional, List

class BaseFactor(ABC):
    """因子基类,所有因子必须继承此类"""
    
    def __init__(self, name: str, category: str):
        self.name = name
        self.category = category  # 'technical' | 'fundamental' | 'ai'
        self.params = {}
        self.data: Optional[pl.DataFrame] = None
        
    @abstractmethod
    def calculate(self, market_data: pl.DataFrame) -> pl.DataFrame:
        """
        计算因子值
        :param market_data: 包含OHLCV的市场数据
        :return: 包含因子值的DataFrame
        """
        pass
    
    def validate(self, df: pl.DataFrame) -> bool:
        """验证因子计算结果"""
        if df.is_empty():
            return False
        if df.null_count().sum_horizontal() > len(df) * 0.1:
            print(f"警告: {self.name} 存在超过10%的空值")
        return True
    
    def get_params(self) -> dict:
        return self.params
    
    def set_params(self, **kwargs):
        self.params.update(kwargs)
        
    def __repr__(self):
        return f"{self.__class__.__name__}(name={self.name}, params={self.params})"


使用示例

class MACDFactor(BaseFactor): """MACD因子""" def __init__(self, fast: int = 12, slow: int = 26, signal: int = 9): super().__init__(name="MACD", category="technical") self.set_params(fast=fast, slow=slow, signal=signal) def calculate(self, market_data: pl.DataFrame) -> pl.DataFrame: # 计算EMA for period in [self.params['fast'], self.params['slow'], self.params['signal']]: market_data = market_data.with_columns([ pl.col('close').ewm_mean(span=period).alias(f'ema_{period}') ]) # 计算MACD market_data = market_data.with_columns([ (pl.col('ema_12') - pl.col('ema_26')).alias('macd'), pl.col('macd').ewm_mean(span=self.params['signal']).alias('macd_signal'), (pl.col('macd') - pl.col('macd_signal')).alias('macd_hist') ]) return market_data.select(['date', 'code', 'macd', 'macd_signal', 'macd_hist'])

四、因子库存储方案:Polars + DuckDB

很多人用MySQL或MongoDB存因子数据,其实完全没必要。对于量化因子这种结构化时序数据,Polars + DuckDB是性价比最高的选择

实测数据:1000只股票、300个因子、5年日线数据,用DuckDB查询平均耗时1.2秒,存储空间比CSV小60%。

# factors/factor_repository.py
import duckdb
from datetime import datetime
import polars as pl
from typing import Optional, List
from pathlib import Path

class FactorRepository:
    """因子数据仓库"""
    
    def __init__(self, db_path: str = "factors.db"):
        self.conn = duckdb.connect(db_path)
        self._init_tables()
        
    def _init_tables(self):
        """初始化表结构"""
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS factor_values (
                date DATE,
                code VARCHAR,
                factor_name VARCHAR,
                factor_value DOUBLE,
                PRIMARY KEY (date, code, factor_name)
            )
        """)
        self.conn.execute("""
            CREATE INDEX IF NOT EXISTS idx_date ON factor_values(date)
        """)
        self.conn.execute("""
            CREATE INDEX IF NOT EXISTS idx_code ON factor_values(code)
        """)
        
    def save_factor(self, factor_name: str, df: pl.DataFrame):
        """保存单个因子"""
        df = df.with_columns([
            pl.lit(factor_name).alias('factor_name')
        ])
        self.conn.execute("DELETE FROM factor_values WHERE factor_name = ?", [factor_name])
        self.conn.execute("INSERT INTO factor_values SELECT * FROM df")
        
    def load_factor(self, factor_name: str, 
                    start_date: Optional[str] = None,
                    end_date: Optional[str] = None,
                    codes: Optional[List[str]] = None) -> pl.DataFrame:
        """加载因子数据"""
        query = f"SELECT * FROM factor_values WHERE factor_name = '{factor_name}'"
        
        if start_date:
            query += f" AND date >= '{start_date}'"
        if end_date:
            query += f" AND date <= '{end_date}'"
        if codes:
            codes_str = "', '".join(codes)
            query += f" AND code IN ('{codes_str}')"
            
        result = self.conn.execute(query).fetch_arrow