Skip to content
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.

  • Updated

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.

Use-case diagram for BatchBacktesting: Build the ticker universe; Download daily prices; Backtest one strategy in batch; Run all strategies on a ticker; Compare returns to buy & hold; Open the HTML trade chartsBatchBacktestingBuild the ticker universeDownload daily pricesBacktest one strategy in batchRun all strategies on a tickerCompare returns to buy & holdOpen the HTML trade chartsStrategy researcher«system»FMP price API«system»Wikipedia S&P 500 list
People on the left, external systems on the right; each oval is a use case backed by the files cited below.
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

  1. Install the dependencies with pip install -r requirements.txt and put an FMP_API_KEY in the environment (see .env.exemple).
  2. Run jupyter notebook and open batch.ipynb.
  3. Run the cell tickers = get_SP500() then run_backtests(tickers, strategy=EMA, num_threads=12, generate_plots=True).
  4. Check output/EMA-<start>-<end>.csv for the combined results and output/charts/EMA/ for one HTML chart per ticker.
  5. 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.

4. Features and components

ComponentWhat it does
Instrument runnerPicks the price adapter, cleans observations, runs one Backtest, tags the result with instrument and strategy names
Worker poolThreadPoolExecutor, four workers by default for the single-strategy runner; the multi-strategy helper nests an outer pool around inner pools
Strategy defaults100,000 starting cash, 0.002 commission (0.2%), exclusive orders
EMA strategy20-, 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
OutputsPer-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() in analyse.py takes 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.

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.

Package table 5
External dependencies
PackageVersionScopePurposeRegistry
backtesting==0.3.3runtimeTestingpypi.org
concurrent-futures==1.3.0runtime—pypi.org
numpy==1.26.0runtimeData analysispypi.org
pandas==1.3.5runtimeData analysispypi.org
pandas-ta==0.3.15runtimeData analysispypi.org

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

  1. AlgoÉTS. (2026, November 5). [Web page] github.com.
  2. AlgoETS. (2026, November 5). src/BatchBacktesting.py. [Source file or documentation] github.com.
  3. AlgoETS. (2026, November 5). src/strategies.py. [Source file or documentation] github.com.
  4. AlgoETS. (2026, November 5). src/analyse.py. [Source file or documentation] github.com.
  5. output/charts/EMA. (2026, November 5). [Web page] algoets.github.io.
  6. AlgoETS. (2026, November 5). BatchBacktesting. [Software repository] github.com.
  7. 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.