Quant Trading for Programmers 11: From One Stock To A Portfolio Backtest
Quant Trading for Programmers 11: From One Stock To A Portfolio Backtest
Article 10 completed the smallest single-stock backtest loop. A single-symbol backtest is useful for explaining rules, but it cannot represent how a strategy behaves across a stock universe.
This article expands the backtest from one stock to a multi-symbol portfolio. We will not start with complex portfolio optimization. The first version only does equal-weight capital allocation, runs each symbol independently, aggregates the equity curves, and summarizes trades. It is simple, but enough to turn “run the strategy across a stock pool” into real code.

Start Portfolio Backtesting With Equal Weight
Portfolio backtesting has many hard problems: rebalance cycles, weight constraints, industry exposure, correlation, trading capacity, and cash reuse.
All of them matter, but they should not all be packed into the first version. We start with a clear boundary:
- Input a list of stock symbols.
- Allocate the same initial capital to each symbol.
- Call
run_signal_backtest()from article 10 for each symbol. - Add all per-symbol equity curves by date.
Equal weight is not the optimal answer. It is the easiest baseline to verify. Its main advantage is a clean accounting rule: if portfolio performance changes, first inspect each symbol’s own backtest result, then inspect the aggregated equity curve. This keeps the first implementation away from optimizers, constraint matrices, and industry-exposure logic. Before production, position caps, industry concentration, suspension handling, capacity checks, and rebalancing rules still need to be added.
Add A Portfolio Result Object
Chapter 11 adds app/portfolio_backtest.py.
The result object is:
@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], ...]
We keep symbol_results instead of only keeping aggregated metrics. When strategy performance needs investigation later, we must know which stocks contributed returns and which stocks created drawdowns.
Equal-Weight Capital Allocation
The core function is 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
)
There are two details here.
First, symbols are deduplicated before running:
selected = list(dict.fromkeys(symbols))
Second, max_symbols can limit backtest size, so local experiments do not accidentally run too large.
Aggregate The Equity Curve
The single-symbol backtest already has daily equity. The portfolio layer sums equity by date:
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())]
This is still a simplified version. A real portfolio backtest must handle suspensions, uneven trading calendars, shared cash, and rebalancing. The first version only makes portfolio equity runnable.
Trade Summary
The portfolio layer also adds 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,
}
This summary is practical. A strategy that trades only 1 stock out of 100 is very different from a strategy that generates signals on 80 stocks.
Real Run Screenshot
The current integrated example continues into portfolio backtesting after the article 10 single-symbol backtest:
uv run python -m scripts.chapter_examples factor-backtest --source sample
Here is the real output from the portfolio section:

This sample has 2 symbols, final portfolio equity of 102515.64, total return around 2.52%, and max drawdown around -1.29%. trade_summary shows that only 1 symbol traded. That is useful information: the portfolio result looks acceptable, but signal coverage is narrow. Later reviews should not only look at total portfolio return.
Chapter Update And Repository
This chapter adds:
app/portfolio_backtest.py.- Equal-weight multi-symbol portfolio backtesting, equity-curve aggregation, trade summary, and date slicing.
tests/test_portfolio_backtest.py, covering capital allocation, deduplication, symbol limits, trade summary, and date filtering.- A real current-mainline screenshot for the portfolio-backtest example.
- Background notes on equal-weight baselines, signal coverage, and later portfolio constraints.
Repository:
https://github.com/ax2/zi-quant-platform
Code for this chapter:
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
The full chapter 11 test suite passes with 172 passed, still with only the existing FastAPI deprecation warning.
Summary
The first step in portfolio backtesting is not weight optimization. It is wiring the multi-symbol execution path.
Equal-weight allocation, per-symbol backtests, equity aggregation, and trade summaries let a strategy move from one stock to a stock pool. The next article adds backtest metrics, so portfolio results are more than just one equity curve.
Follow ZiCode on WeChat
If this post was useful, you can follow later updates on WeChat as well.
X / Twitter
Follow @ax2_zicode
Faster technical notes, short thoughts, and new-post alerts are posted on X.
More in this column
- Quant Trading for Programmers 15: Strategy Promotion Needs A Gate
- Quant Trading for Programmers 14: Save Strategy Candidates As Experiment Records
- Quant Trading for Programmers 13: Add A First Grid Search For Strategy Parameters
- Quant Trading for Programmers 12: Final Return Is Not Enough
- Quant Trading for Programmers 10: Run The First Minimal Backtest Loop
- Quant Trading for Programmers 09: From K-Lines To The First Explainable Factor Signal
- Quant Trading for Programmers 08: Clean Raw K-Lines Into Trustworthy Market Bars
- Quant Trading for Programmers 07: Build A Clean A-Share Stock Universe First