程序员量化交易实战 11:从单只股票扩展到多标的组合回测
原创 · 约 9 分钟阅读 · 阅读 --
Last updated on

程序员量化交易实战 11:从单只股票扩展到多标的组合回测

作者: Alex Xiang


程序员量化交易实战 11:从单只股票扩展到多标的组合回测

古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。这个专栏会把一个 A 股量化平台从 0 到 1 拆开写:数据、策略、回测、模拟盘、提醒和生产化,尽量用真实代码和真实运行结果说话。更完整的更新也会同步到微信公众号「字与码」。

第 10 篇跑通了单只股票的最小回测闭环。单标的回测能解释规则,但不能代表策略在股票池上的表现。

这一篇把回测从一只股票扩展到多标的组合。先不做复杂组合优化,只做等权资金分配、逐标的运行、权益曲线聚合和交易统计。简单,但足够把“策略在股票池上跑一遍”这件事落到代码。

程序员量化交易实战第十一篇封面

组合回测先从等权开始

组合回测有很多复杂问题:调仓周期、权重约束、行业暴露、相关性、成交量容量、现金复用。

这些都重要,但第一版先不要全塞进去。我们先做一个明确边界:

  • 输入一组股票代码。
  • 每只股票分到相同初始资金。
  • 每只股票调用第 10 篇的 run_signal_backtest()
  • 最后把各股票权益曲线按日期相加。

多标的组合回测面板

新增组合结果对象

第 11 章新增 app/portfolio_backtest.py

组合结果对象是:

@dataclass(frozen=True)
class PortfolioBacktestResult:
    initial_cash: float
    final_equity: float
    total_return: float
    max_drawdown: float
    symbol_results: tuple[MiniBacktestResult, ...]
    equity_curve: tuple[dict[str, object], ...]

这里保留 symbol_results,不是只保留聚合指标。因为后面排查策略表现时,必须能知道是哪只股票贡献了收益,哪只股票拖累了回撤。

等权资金分配

核心函数是 run_equal_weight_portfolio_backtest()

cash_per_symbol = initial_cash / len(selected)
results = tuple(
    run_signal_backtest(
        symbol,
        all_bars,
        initial_cash=cash_per_symbol,
        position_ratio=position_ratio,
    )
    for symbol in selected
)

这里有两个细节。

第一,symbols 会先去重:

selected = list(dict.fromkeys(symbols))

第二,可以用 max_symbols 限制回测规模,避免本地实验一次跑太大。

聚合权益曲线

单标的回测已经有每日权益。组合层做的是按日期求和:

def _sum_equity_by_date(results: Iterable[MiniBacktestResult]) -> list[dict[str, object]]:
    by_date: dict[str, float] = {}
    for result in results:
        for row in result.equity_curve:
            trade_date = str(row["trade_date"])
            by_date[trade_date] = by_date.get(trade_date, 0.0) + float(row["equity"])
    return [{"trade_date": trade_date, "equity": round(equity, 2)} for trade_date, equity in sorted(by_date.items())]

这仍然是简化版。真实组合回测要处理不同股票停牌、日期不齐、现金共享和调仓。但第一版先让组合权益能跑出来。

交易统计

组合层还加了 portfolio_trade_summary()

{
    "symbols": len(result.symbol_results),
    "traded_symbols": len(traded_symbols),
    "buy_count": buy_count,
    "sell_count": sell_count,
    "trade_count": buy_count + sell_count,
}

这个摘要很实用。策略如果在 100 只股票上只交易了 1 只,和在 80 只股票上都有信号,是完全不同的解释。

本章更新与代码仓库

本章更新内容:

  • 新增 app/portfolio_backtest.py
  • 实现等权多标的组合回测、权益曲线聚合、交易统计和日期裁剪。
  • 新增 tests/test_portfolio_backtest.py,覆盖资金分配、去重限额、交易摘要和日期过滤。

代码仓库:

https://github.com/ax2/zi-quant-platform

本章代码:

git clone https://github.com/ax2/zi-quant-platform.git
cd zi-quant-platform
git checkout chapter-11
uv sync --extra dev
uv run pytest tests/test_portfolio_backtest.py

第 11 章全量测试通过:172 passed,仍只有既有 FastAPI deprecation warning。

本篇小结

组合回测的第一步不是优化权重,而是把多标的运行链路接上。

等权分配、逐标的回测、权益聚合和交易摘要,让策略可以从单只股票走向股票池。下一篇继续补回测指标,让组合结果不只是一条收益曲线。