当前位置:首页>python>用 Python 面向对象思想搭建量化回测系统,年化收益 14.84%

用 Python 面向对象思想搭建量化回测系统,年化收益 14.84%

  • 2026-09-29 12:29:34
用 Python 面向对象思想搭建量化回测系统,年化收益 14.84%

2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!

引言

很多数据科学家在从分析脚本转向生产级交易系统时,都会遇到同样的问题:最初的脚本往往是单体式的,依赖全局状态,当需要处理多资产、模拟交易成本或者整夜运行时,这些脚本就会崩溃。

本文将带你从零开始,使用面向对象编程(OOP)的思想,构建一个高度解耦、有状态的量化回测系统。这个系统能够在股票池中运行复杂策略,同时精确模拟资金部署和交易摩擦成本。

最终,我们的 50/20 均线交叉策略取得了 14.84% 的年化收益率(CAGR),略微跑赢了基准的 14.82%。

系统架构概览

整个回测流程分为五个核心模块,每个模块都封装在一个独立的类中:

  1. 1. DataManager:数据获取与清洗
  2. 2. MomentumStrategy:策略信号生成
  3. 3. Portfolio:投资组合状态管理
  4. 4. BacktestEngine:回测引擎执行
  5. 5. PerformanceEvaluator:绩效评估

这种严格的依赖倒置设计,确保了数据处理逻辑与信号逻辑的完全解耦。

第一步:数据获取与清洗

量化回测中一个常见的陷阱是混淆收盘价(Close)和复权收盘价(Adjusted Close)。我们的 DataManager 类强制使用 auto_adjust=False 获取原始数据,然后显式选择 Adj Close 列。

class DataManager:    def _download_data(self) -> pd.DataFrame:        """下载数据,使用 auto_adjust=False 以便显式管理复权价格"""        tickers = self.config.TICKERS + [self.config.BENCHMARK_TICKER]        # 关键:设置 auto_adjust=False,后续手动选择 Adj Close        data = yf.download(            tickers,            start=self.config.START_DATE,            end=self.config.CURRENT_DATE,            interval="1d",            auto_adjust=False,            progress=False        )        return data    def _clean_and_structure_data(self, raw_data: pd.DataFrame) -> pd.DataFrame:        """清洗数据并转换为干净的结构:日期为索引,股票代码为列"""        # 关键步骤:只选择复权收盘价        price_df = raw_data['Adj Close'].copy()        price_df.index.name = 'Date'        # 检查缺失值并使用前向填充        if price_df.isnull().values.any():            logger.warning(                f"DataManager 发现 {price_df.isnull().sum().sum()} 个缺失值,正在前向填充..."            )            price_df = price_df.ffill()        return price_df

第二步:策略信号生成

在信号生成环节,最重要的是避免前视偏差(Lookahead Bias)。交叉信号只有在交叉确实发生并在交易日收盘时得到确认后才能生成。我们通过 shift(1) 方法来实现这一点。

class MomentumStrategy(StrategyBase):    def generate_signals(self) -> pd.DataFrame:        """生成交易信号,严格避免前视偏差"""        for ticker in self.price_data.columns:            prices = self.price_data[ticker]            # 计算短期和长期均线            short_ma = prices.rolling(window=self.short_window).mean()            long_ma = prices.rolling(window=self.long_window).mean()            # 信号生成逻辑:使用 shift(1) 确保基于前一日指标状态            # 买入信号:短期均线从下方突破长期均线            buy_signal = (short_ma.shift(1) < long_ma.shift(1)) & (short_ma > long_ma)            # 卖出信号:短期均线从上方跌破长期均线            sell_signal = (short_ma.shift(1) > long_ma.shift(1)) & (short_ma < long_ma)            signals = pd.Series(0, index=prices.index)            signals[buy_signal] = 1            signals[sell_signal] = -1            # 持仓传递:保持仓位直到下一个明确信号            signals = signals.replace(0, method='ffill').fillna(0)        # 最终步骤:将信号再偏移一天        # 确保 T 日生成的信号在 T+1 日执行        return final_signals.shift(1).fillna(0)

第三步:投资组合与回测引擎

Portfolio 类管理系统状态,包括现金、当前持仓和交易记录。关键的结构性检查是成本建模,交易佣金(0.1%)会立即计入现金流计算。

class Portfolio:    def execute_trade(self, date: pd.Timestamp, ticker: str, signal: int, price: float):        """根据信号执行交易"""        if signal == 1:  # 买入            # 计算可买入的股数            # 佣金计入总支出            cost = shares_to_trade * price * (1 + self.commission)            if cost > self.cash:                # 资金不足时重新计算                shares_to_trade = int(self.cash / (price * (1 + self.commission)))                if shares_to_trade == 0:                    return                cost = shares_to_trade * price * (1 + self.commission)            self.cash -= cost        elif signal == -1:  # 卖出            # 佣金从收入中扣除            proceeds = shares_to_trade * price * (1 - self.commission)            self.cash += proceeds

BacktestEngine 遍历每个交易日,先根据当日收盘价计算投资组合价值,记录权益曲线,然后执行当日的交易信号。

class BacktestEngine:    def run(self):        """运行回测"""        logger.info("--- 阶段 2:回测执行 ---")        signals = self.strategy.generate_signals()        for date in trading_dates:            current_prices = self.price_data.loc[date].to_dict()            # 1. 投资组合估值            equity = self.portfolio.get_equity(current_prices, date)            self.history.loc[date, 'TotalEquity'] = equity            # 2. 信号应用与执行            day_signals = signals.loc[date]            for ticker in self.config.TICKERS:                signal = day_signals.get(ticker, 0)                price = current_prices.get(ticker)                if price is not None and signal != 0:                    # 基于当日收盘价执行                    self.portfolio.execute_trade(date, ticker, signal, price)        logger.info(f"回测完成,共模拟 {len(trading_dates)} 个交易日")

第四步:绩效评估

PerformanceEvaluator 类将原始收益转换为可操作的统计指标,包括年化收益率、夏普比率和胜率。

class PerformanceEvaluator:    def evaluate(self):        """评估策略表现"""        logger.info("--- 阶段 3:绩效评估 ---")        strategy_equity = self.equity_curve['TotalEquity']        # 计算策略收益        strategy_daily_returns, strategy_cagr = self._calculate_returns(strategy_equity)        # 计算交易指标        total_trades = len(self.trades_df)        winning_trades = len(self.trades_df[self.trades_df['Cash_Change'] > 0])        self.results = {            "CAGR (%)": strategy_cagr * 100,            "Total Trades": total_trades,            "Winning Trades": winning_trades,            "Win Rate (%)": (winning_trades / total_trades) * 100 if total_trades > 0 else 0,            "Benchmark CAGR (%)": bench_cagr * 100,            "Outperformance vs Benchmark (%)": (strategy_cagr - bench_cagr) * 100        }

第五步:风险分析

最大回撤(Maximum Drawdown)是衡量风险的关键指标,通过比较当前权益与历史峰值来计算。

class PerformanceEvaluator:    def evaluate(self):        # 计算最大回撤        cumulative_returns = strategy_equity / strategy_equity.cummax()        mdd = (cumulative_returns.min() - 1) * 100  # 转换为百分比

回测结果分析

运行完整回测后,我们得到以下关键指标:

指标
数值
总收益率
127.64%
年化收益率(CAGR)
14.84%
夏普比率
0.60
最大回撤
-26.15%
总交易次数
10225
盈利交易次数
495
胜率
4.84%
基准年化收益率
14.82%
超额收益
0.02%

4.84% 的胜率是最值得关注的统计数据。每 100 笔交易中,只有不到 5 笔是净盈利的。策略之所以能够成功,是因为少数盈利交易的收益足够大,能够覆盖 95% 亏损交易的累计损失以及交易佣金。

下一步优化方向

这个框架是一个稳健的起点。要迈向生产级系统,可以从以下几个方向进行优化:

  1. 1. 滑点模型:当前模型使用收盘价执行。下一步可以在 BacktestEngine 中引入 SlippageModel,根据交易规模与日均成交量的比例动态调整价格。
  2. 2. 仓位管理:用凯利公式或波动率仓位管理替代固定 10% 的现金分配规则。
  3. 3. 流动性约束:创建 UniverseFilter 模块,基于历史成交量指标限制交易规模,防止在流动性不足时执行不切实际的交易。
  4. 4. 夏普比率分解:进一步分析标准差中交易成本与市场噪音各自的占比。

总结

本文通过面向对象编程的思想,构建了一个完整的多资产量化回测系统。系统将数据获取、信号生成、交易执行和绩效评估解耦为独立的模块,这种模块化设计是系统最大的优势,允许进行有针对性的安全迭代。

50/20 均线交叉策略在 2020 年至 2025 年间取得了 14.84% 的年化收益率,略微跑赢基准。然而,4.84% 的胜率揭示了策略的结构性约束:它本质上是嘈杂的,依赖少数大赢来覆盖众多小亏损的成本。26.15% 的最大回撤表明,即使净收益与基准持平,这种方法的固有波动性也需要相当的心理承受能力。

参考文章

  1. 加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:

  1. 1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架;
  2. 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程;
  3. 3. 量化文章专题教程库:300+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考;
  4. 4. PyBroker实战课程:赠送《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能;
  5. 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础;
  6. 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践;专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题;
  7. 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。

星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐

最新文章

随机文章