Table of contents
BatchBacktesting
BatchBacktesting turns a backtest into a repeatable batch: choose instruments, obtain and clean prices, run a strategy, and save comparable results. It is an AlgoÉTS repository; the facts below come from its source files and README as read for the companion article.
1. Overview
Why it exists. A batch backtest is a small data pipeline, and the project keeps every step visible in Python instead of behind a dashboard.
Best fit. Comparing one strategy across many instruments with the same recorded assumptions, then reading the per-instrument and aggregate CSVs side by side.
2. Use cases
AlgoÉTS notebooks that backtest indicator strategies on S&P 500 stocks and cryptos in parallel, with CSV and charts.
- Build the ticker universe
- Strategy researcherWikipedia S&P 500 list
get_SP500() reads the S&P 500 symbol table from Wikipedia with pandas.read_html, and get_all_crypto() returns a fixed list of crypto pairs such as BTCUSD and ETHUSD.
- Download daily prices
- Strategy researcherFMP price API
process_instrument calls the FMP historical-price-full endpoint for stocks or the crypto variant, with FMP_API_KEY read from the environment in src/config.py.
- Backtest one strategy in batch
- Strategy researcherFMP price API
run_backtests(tickers, strategy=EMA, num_threads=12, generate_plots=True) runs backtesting.py on every ticker in a thread pool and writes output/raw/<Strategy>/<ticker>-<start>-<end>.csv.
- Run all strategies on a ticker
- Strategy researcher
run_backtests_strategies(["BMO"], STRATEGIES_STR) submits every strategy defined in src/strategies.py (EMA, RSI, MACD, Bollinger Bands, ADX, TRIX…) for the given instruments.
- Compare returns to buy & hold
- Strategy researcher
analyse.ipynb loads output/raw/<Strategy>/*.csv, ranks instruments by Return [%] and plots the top five against Buy & Hold Return [%] with matplotlib.
- Open the HTML trade charts
- Strategy researcher
With generate_plots=True, plot_results saves each backtesting.py chart as output/charts/<Strategy>/<ticker>-<start>-<end>.html; the repository already holds charts for 23 strategies.
2.1. Scenario: Test EMA on the S&P 500
- Install the dependencies with pip install -r requirements.txt and put an FMP_API_KEY in the environment (see .env.exemple).
- Run jupyter notebook and open batch.ipynb.
- Run the cell tickers = get_SP500() then run_backtests(tickers, strategy=EMA, num_threads=12, generate_plots=True).
- Check output/EMA-<start>-<end>.csv for the combined results and output/charts/EMA/ for one HTML chart per ticker.
- Open analyse.ipynb and run the EMA section to list the top returns and compare them with buy and hold.
Files readREADME.mdbatch.ipynbanalyse.ipynbsrc/BatchBacktesting.pysrc/data.pysrc/config.pysrc/strategies.pyrequirements.txt.env.exemple
3. Getting started
The README links a published example rather than a one-command install; the runner is the entry point.
- Runner:
src/BatchBacktesting.py—process_instrumentselects a stock or crypto price adapter, cleans the data and constructs aBacktest. - Strategies:
src/strategies.py. - Analysis helpers:
src/analyse.py. - Published AAPL EMA chart (2018-04-04 to 2023-04-03): output/charts/EMA.
4. Features and components
| Component | What it does |
|---|---|
| Instrument runner | Picks the price adapter, cleans observations, runs one Backtest, tags the result with instrument and strategy names |
| Worker pool | ThreadPoolExecutor, four workers by default for the single-strategy runner; the multi-strategy helper nests an outer pool around inner pools |
| Strategy defaults | 100,000 starting cash, 0.002 commission (0.2%), exclusive orders |
| EMA strategy | 20-, 80- and 150-period EMAs computed; entries compare the 20 and 80 curves; long side closes the prior position and buys with a 90% stop and a 125% target |
| Outputs | Per-instrument CSV, aggregate CSV per strategy, optional HTML chart |
5. How the strategies decide
EMA crossover. The default strategy computes exponential moving averages of the close, EMAₜ = α · Pₜ + (1 − α) · EMAₜ₋₁ with α = 2 / (n + 1): 0.095 for the 20-period average and 0.025 for the 80-period one, so the short average reacts about four times faster. On each bar:
- when EMA 20 crosses above EMA 80, the strategy closes any position and buys, with a stop at 90% and a target at 125% of the close;
- when EMA 80 crosses above EMA 20, it closes and sells short, with a stop at 110% and a target at 75%.
The 150-period average is computed but never used.
What the exits imply. With a stop 10% away and a target 25% away, a long trade risks 10 to make 25. Ignoring fees, it breaks even if more than 10 / (10 + 25) ≈ 28.6% of trades reach the target before the stop. Each round trip also pays the 0.2% commission twice, which raises that threshold slightly. Because the next crossover closes the position first, many trades end on a crossover rather than on the stop or the target.
flowchart TD
B["New bar"] --> C1{"EMA20 crosses above EMA80?"}
C1 -->|yes| L["Close position, buy: SL 0.90 × close, TP 1.25 × close"]
C1 -->|no| C2{"EMA80 crosses above EMA20?"}
C2 -->|yes| S["Close position, sell short: SL 1.10 × close, TP 0.75 × close"]
C2 -->|no| H["Hold; the broker checks SL and TP"]
The other strategies. src/strategies.py defines twenty-three indicator strategies (RSI, Bollinger Bands, MACD, linear regression, ADX, OBV, Aroon, DEMA, ALMA, SMA …). Most follow the same pattern, and fourteen of them use the same 90/125% and 110/75% exits. Two of them do not test what their names suggest. In RSI, rsi30 and rsi70 are RSIs computed over 30 and 70 bars, not the 30 and 70 levels, so the strategy trades crossings between three RSI curves. BollingerBands uses only the first column of pandas_ta.bbands, the lower band.
Aggregation. process_instrument tags each Backtest.run() result with the instrument and the strategy, the runner concatenates them into one CSV per strategy, and analyse.py ranks instruments. get_max_return groups by instrument and takes max() column by column, so the “best” row mixes the best return with the best value of every other column.
6. Architecture
flowchart TD
I["Instrument list and strategy"] --> P["ThreadPoolExecutor"]
P --> D["Fetch and clean historical data"]
D --> B["Backtest per instrument"]
B --> R["Result with Instrument and Strategy"]
R --> C["Per-instrument CSV"]
R --> A["Aggregate strategy CSV"]
B --> H["Optional HTML chart"]
7. Constraints and checks
- A failed instrument returns
None; if every instrument fails, the later concatenation has no frames. groupby("Instrument").max()inanalyse.pytakes each column’s maximum independently, so a summary row can mix metrics from different runs. Select a real row by return before quoting its drawdown.- The defaults do not cover slippage, short availability, delisted securities or corporate actions.
- Worker counts and dates above are code defaults and example dates, not measured throughput or a fixed range.
8. Links
- Repository
- Published example
- Workshop1, the beginner notebooks that lead into this batch runner
9. CI/CD pipeline
No CI/CD pipeline in the repository. The code is built and run by hand, as described above.
Generated from the source at commit 6aae4af.
10. Deployment diagram
Mermaid Deployment diagram
flowchart LR
repo[("GitHub repository")]
local["Local machine<br/>Python"]
repo -->|"clone and run"| local
11. External dependencies
5 declared packages (5 runtime, 0 development) in requirements.txt.
12. Glossary
Repository
The versioned source files, configuration, and history of a project.
README
The repository’s entry document, usually covering its purpose and initial setup.
Dependency
A library, tool, or service that another component needs to build or run.
HTTP
A request-and-response protocol used by browsers and web services. A method, path, headers, and optional body describe a request.
CLI
A text interface in which commands and arguments select operations. Exit codes let scripts detect success or failure.
Continuous integration
Automated checks that run when source changes, such as building, testing, and linting. Passing checks cover only what those checks exercise.
Mermaid
A text-based diagram language. Nodes and connections describe a model; the diagram itself does not execute the project.
13. References
- AlgoÉTS. (2026, November 5). [Web page] github.com.
- AlgoETS. (2026, November 5). src/BatchBacktesting.py. [Source file or documentation] github.com.
- AlgoETS. (2026, November 5). src/strategies.py. [Source file or documentation] github.com.
- AlgoETS. (2026, November 5). src/analyse.py. [Source file or documentation] github.com.
- output/charts/EMA. (2026, November 5). [Web page] algoets.github.io.
- AlgoETS. (2026, November 5). BatchBacktesting. [Software repository] github.com.
- Published example. (2026, November 5). [Web page] algoets.github.io.
Sources without a publication date show the date they were last checked, November 5, 2026. File links retain the inspected revision where available.