Skip to content
Table of contents

nova-backtest

nova-backtest is a Python library for backtesting crypto trading strategies, published on PyPI as novalabs-backtest (version 1.1.10 in pyproject.toml). It downloads historical candles from Binance, Bybit, OKX, KuCoin, Huobi, MEXC, BTCEX and Oanda, runs a strategy over many pairs at once and reports statistics. This repository is an archived fork of the NovaLabs project; the commits made here in April 2023 rename the package directory and update the README. The interest of the code is how it simulates trades without a bar-by-bar loop.

Python libraryForkArchivedAlgorithm
PythonpandasNumPyPlotlyPoetrypytestSphinx
  • Updated

1. Overview

A strategy is a subclass of BackTest (novalabs-backtest/utils/backtest.py) that fills three methods on a pandas DataFrame of candles:

  • build_indicators(df) adds indicator columns;
  • entry_strategy(df) sets entry_signal (1 long, −1 short, NaN nothing), take_profit, stop_loss and position_size on the signal candle;
  • exit_strategy(df) sets exit_signal (or NaN everywhere).

The constructor takes the exchange, the candle interval, the pairs (a list, "All pairs" or "Random N"), the period, the starting bankroll, the leverage, the maximum number of open positions and the maximum holding time. Each position gets leverage / max_pos of the bankroll, and the exchange’s maker fee comes from FEES in utils/constant.py (Binance 0.02%, Bybit 0.01%, OKX 0.015%).

2. Use cases

Python library to download crypto exchange candles and backtest a trading strategy with profit and drawdown stats.

Use-case diagram for nova-backtest: Download historical candles; Pick an exchange client; Define entry and exit rules; Run the backtest; Save performance statistics; Build indicator featuresnova-backtestDownload historical candlesPick an exchange clientDefine entry and exit rulesRun the backtestSave performance statisticsBuild indicator featuresStrategy developer«system»Exchange REST API
People on the left, external systems on the right; each oval is a use case backed by the files cited below.
Download historical candles
Strategy developerExchange REST API

BackTest.get_historical_data in novalabs-backtest/utils/backtest.py calls the exchange client and caches OHLCV data in database/<exchange>/hist_<pair>_<candle>.csv, starting from 2019-01-01.

Pick an exchange client
Strategy developerExchange REST API

clients() in novalabs-backtest/clients/clients.py returns a client for Binance, Bybit, OKX, Huobi, KuCoin, MEXC, Oanda or BTCEX, each following BackTestClientInterface.

Define entry and exit rules
Strategy developer

A strategy subclasses BackTest and fills build_indicators, entry_strategy (entry_signal, take_profit, stop_loss, position_size) and exit_strategy (exit_signal).

Run the backtest
Strategy developer

run_backtest simulates positions per pair with exchange fees, a maximum number of positions and a maximum holding time, then plots cumulative profit.

Save performance statistics
Strategy developer

The run writes results/<strategy>_pairs_stats.csv and results/<strategy>_overall_stats.json with returns, drawdown and per-pair figures.

Build indicator features
Strategy developer

TechnicalIndicatorsCreation in novalabs-backtest/utils/indicators.py adds lags, close ratios and support or resistance levels to a candle DataFrame.

2.1. Scenario: Backtest a MACD strategy on Binance pairs

  1. Install the dependencies listed in requirements.txt (the README's pip command spells the package novelabs-backtest).
  2. Subclass BackTest with exchange="binance", candle="1h", list_pairs=["BTCUSDT", "ETHUSDT"], a start bankroll, leverage and max_pos.
  3. Compute the MACD histogram in build_indicators and set entry_signal, take_profit and stop_loss in entry_strategy.
  4. Call run_backtest(); missing pairs are downloaded into database/binance/ on first use.
  5. Read the profit chart and the JSON and CSV files written under results/.

Files readREADME.mdnovalabs-backtest/utils/backtest.pynovalabs-backtest/clients/clients.pynovalabs-backtest/clients/client_interface.pynovalabs-backtest/utils/indicators.pyrequirements.txt

3. How a trade is simulated

run_backtest processes each pair with whole-column operations, then merges the pairs.

flowchart TD
  D["get_historical_data(pair): candles from the exchange client"] --> I["build_indicators"]
  I --> E["entry_strategy: entry_signal, take_profit, stop_loss"]
  E --> V["_verify_tp_sl: TP and SL on the right side of the price"]
  V --> P["_create_entry_prices_times: enter at the NEXT candle's open"]
  P --> X["exit_strategy: exit_signal"]
  X --> L["_create_closest_exit: look ahead i = 1 … N−1 candles"]
  L --> F["_create_all_exit_point: earliest of SL, TP, max holding, exit signal"]
  F --> O["_create_position_df: drop trades that overlap on the same pair, price the exit, compute P&L and fees"]
  O --> A["_all_pairs_real_positions: merge pairs, sort by exit, drop trades beyond max_pos"]
  A --> S["_create_full_statistics + Plotly equity graph"]
  1. Entry. A signal on candle t is filled at the open of candle t + 1, so the strategy cannot trade on the close it just used to decide.
  2. Look-ahead. _create_closest_exit converts the maximum holding time into a number of candles N and builds, for each shift i from 1 to N − 1, a column with the time candle t + i touched the stop or the target. For a long, the stop is hit when that candle’s low is at or below the stop; the target counts only when the high reaches it and the low stayed above the stop. When one candle touches both, the trade is recorded as stopped out, which is the cautious reading since the order of the two moves inside a candle is unknown. Shorts mirror this.
  3. Exit. The exit time is the earliest of the closest stop, the closest target, entry + maximum holding, and the candle after an opposite exit_signal. Stops and targets fill at their own price; the other two exits fill at the next open.
  4. One trade at a time per pair. Trades whose entry falls before the previous trade’s exit on the same pair are removed.
  5. Profit and fees. On a notional of 100, the move is side × (exit − entry) / entry, and fees are 100 × (2 + move − fee) × fee: one fee on the way in, and one on the exit value after the first fee.
  6. Portfolio cap. All pairs’ trades are merged and sorted by exit time; trades that would open while max_pos positions are already open are deleted, because a real account could not have taken them. Position sizes are leverage / max_pos × bankroll, optionally compounding (geometric_sizes).

Worked trade. The README’s example strategy, MACD_HIST, goes long when the MACD histogram turns positive and puts the target and the stop tp_sl_delta = 0.05 away from the close. Say the next candle opens at 100 and, for simplicity, the signal close was also 100: the target is 105 and the stop 95. Candle t + 2 has a low of 97 and a high of 103, so nothing happens. Candle t + 3 has a low of 96 and a high of 105.5: the high reaches the target and the low stays above the stop, so the trade exits at 105 on that candle. The move is +5% and the Binance fee 0.02%, so the fees on a notional of 100 are 100 × (2 + 0.05 − 0.0002) × 0.0002 = 0.041 and the realised P&L is 5 − 0.041 = 4.959. With a leverage of 2 and max_pos = 4, each position uses half of the bankroll. (The candle values are illustrative; the steps and formulas are the code’s.)

4. Getting started

pip install novalabs-backtest

The README writes pip install novelabs-backtest, with a typo in the package name. A strategy then subclasses BackTest, implements the three methods and calls run_backtest(), which saves results/{strategy}_pairs_stats.csv and results/{strategy}_overall_stats.json. Some exchange clients need API keys passed as key, secret and passphrase.

5. Repository guide

  • novalabs-backtest/utils/backtest.py: the BackTest class (1,262 lines): data, exits, positions, portfolio cap, statistics and plots.
  • novalabs-backtest/clients/: one client per exchange behind client_interface.py, created by the clients() factory.
  • novalabs-backtest/utils/indicators.py, helpers.py, constant.py: indicators, interval conversions and fees.
  • novalabs-backtest/data/: Discord, Reddit, Telegram and order-book collectors.
  • tests/: client tests (format, earliest timestamp, history, pair info, server time, updates) and indicator tests.
  • .github/workflows/: tests, PyPI publishing and documentation builds; docs/ is a Sphinx site.

6. Limits

  • The look-ahead loop stops at N − 1 candles, one short of the maximum holding time, and its cost grows with N: two new columns per candle of holding time.
  • Within one candle, a touch of both stop and target is always counted as a stop, even when the price may have reached the target first.
  • Fees use the maker rate for both legs; market-order fills, funding and slippage are not modelled.
  • The repository is archived, the directory name novalabs-backtest contains a hyphen (so it cannot be imported as written from source), and the README install command has a typo.
  • The README’s MACD_HIST example reads local CSV files and does not subclass BackTest, so it does not show the library’s real API.
  • The README badges point to Nova-DevTeam/nova-backtest and a coverage badge in another repository name (nove-backtest).

7. Sources

8. CI/CD pipeline

3 GitHub Actions workflows, 3 jobs. Each box is a job; double-bordered boxes publish or deploy, and arrows follow needs.

Mermaid Pipeline flow
flowchart LR
  subgraph wf0["ReadTheDocs Build"]
    direction LR
    wf0_deploy[["deploy<br/>ubuntu-latest"]]
  end
  subgraph wf1["Publish Python 🐍 distributions 📦 to PyPI"]
    direction LR
    wf1_build_n_publish[["Build & publish nova-backtest 🐍<br/>ubuntu-latest"]]
  end
  subgraph wf2["Python Test and Build"]
    direction LR
    wf2_deploy["deploy<br/>ubuntu-latest"]
  end
  t0(["push · manual run"])
  t1(["pull request"])
  t2(["push · pull request · manual run"])
  d0[("GitHub Pages")]
  d1[("PyPI")]
  t0 --> wf0_deploy
  wf0_deploy -.-> d0
  t1 --> wf1_build_n_publish
  wf1_build_n_publish -.-> d1
  t2 --> wf2_deploy
Workflows, triggers, jobs and targets
WorkflowTriggersJobsPublishes to
ReadTheDocs Build
.github/workflows/make-docs.yml
push, manual run
  • deploy ubuntu-latest
    actions/checkout · Set up Python · Install dependencies · Build Docs · Deploy
GitHub Pages
Publish Python 🐍 distributions 📦 to PyPI
.github/workflows/publish-to-pypi.yml
pull request
  • Build & publish nova-backtest 🐍 ubuntu-latest
    actions/checkout · Set up Python 3.8 · Install setuptools · Build a binary wheel and a source tarball · Publish distribution 📦 to PyPI
PyPI
Python Test and Build
.github/workflows/run-test.yml
push, pull request, manual run
  • deploy ubuntu-latest
    actions/checkout · Set up Python · Install dependencies · Export Environment Variables · Run Tests · Build package
—

Generated from the source at commit dda19e6.

9. Deployment diagram

Mermaid Deployment diagram
flowchart LR
  repo[("GitHub repository")]
  ci[["GitHub Actions<br/>3 workflows"]]
  image["Docker image"]
  pub_GitHub_Pages[("GitHub Pages")]
  pub_PyPI[("PyPI")]
  repo -->|"push or pull request"| ci
  ci -->|"docker build"| image
  ci -->|"publishes"| pub_GitHub_Pages
  ci -->|"publishes"| pub_PyPI

10. External dependencies

14 declared packages (14 runtime, 0 development) in pyproject.toml, requirements.txt.

Package table 14
External dependencies
PackageVersionScopePurposeRegistry
pandas^2.0.0runtimeData analysispypi.org
numpy^1.24.2runtimeData analysispypi.org
pytz^2023.3runtimeWorld timezone definitions, modern and historicalpypi.org
python-decouple^3.8runtimeStrict separation of settings from code.pypi.org
matplotlib^3.7.1runtimeChartspypi.org
aiohttp^3.8.4runtimeHTTP clientpypi.org
asyncio^3.4.3runtimeDeprecated backport of asyncio; use the stdlib package insteadpypi.org
psaw^0.1.0runtimePushshift.io API Wrapper for reddit.com public comment/submission searchpypi.org
telethon^1.27.0runtimeFull-featured Telegram client library for Python 3pypi.org
requests^2.28.2runtimeHTTP clientpypi.org
ta^0.10.2runtimeMarket data and tradingpypi.org
setuptools—runtimeBuild toolingpypi.org
pytest—runtimeTestingpypi.org
typing—runtimeType checkingpypi.org

11. 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.

API

An interface through which software components exchange requests and results. An API does not have to use HTTP.

HTTP

A request-and-response protocol used by browsers and web services. A method, path, headers, and optional body describe a request.

Published port

A host port mapped to a service’s container port. Publishing a port can expose that service beyond its container network.

CLI

A text interface in which commands and arguments select operations. Exit codes let scripts detect success or failure.

Build

The steps that turn source files and dependencies into a runnable program or distributable artifact.

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.

Fork

A separate repository derived from another project. A change in a fork is not automatically a change in the upstream project.

12. References

  1. antoinebou12. (2026, November 5). nova-backtest. [Software repository] github.com.
  2. antoinebou12. (2026, November 5). nova-backtest: README. [Repository documentation] github.com.
  3. antoinebou12. (2026, November 5). novalabs-backtest/utils/backtest.py. [Source file] github.com.
  4. antoinebou12. (2026, November 5). novalabs-backtest/utils/constant.py. [Source file] github.com.
  5. antoinebou12. (2026, November 5). README.md. [Source file] github.com.
  6. Boucher, A. (2026, October 23). nova-backtest: Historical candles into a strategy simulation. [Project article] antoineboucher.info.

Sources without a publication date show the date they were last checked, November 5, 2026. File links retain the inspected revision where available.