Skip to main content

Trading-Backtester

A Python library for backtesting trading strategies. It allows users to simulate historical trades, evaluate strategy performance, and analyze key metrics such as returns, drawdowns, and risk. The library supports custom indicators, making it suitable for both simple and advanced strategies.

Requirements

  • numpy - for data and numeric manipulation
  • matplotlib - for plotting results

Why numpy?

Of course I could handle most mathematical operation using regular floats number, but NumPy is widely used for such tasks and help make code more structured. What's more, NumPy is supported by many trading indicators libraries - for example, I wanted to use my backtesting engine with TA-Lib, library that takes NumPy arrays as input.

Why another "Backtesting" library

There are many backtesting libraries and platforms available - that's right. However, when I was looking for Python backtesting library that could handle some specific requirements for my strategies - such as placing different orders at market open and closes, detecting price gaps, etc. - I found that the features set of popular libraries were not sufficient.

Additionally, building my own backtesting engine served as a great side project for learing and improving my development skills.

Installation

Library is available on PyPI.

python3 -m pip install trading-backtester

Usage example

Declare RSI Indicator

We use TA-Lib to calculate Relative Strength Indicator values.

class RsiIndicator(Indicator):
    def __init__(self, period: int):
        super().__init__()
        self.__period = period

    def _calc_indicator_values(self, data: Data) -> np.ndarray[Any, np.dtype[Any]]:
        rsi = talib.RSI(data.close, timeperiod=self.__period)
        return rsi

Declare trading strategy

Our example trading strategy is based on the RSI Indicator:

  • When RSI Indicator value crosses above 30 from below:
    • Open a long position
    • Close any existing short position
  • When RSI indicator values crosses below 70 from above:
    • Open short position
    • Close any existing long position
class RsiStrategy(Strategy):
    def __init__(
        self,
    ):
        super().__init__()
        self.__rsi_indicator = RsiIndicator(period=14)

    def collect_orders(
        self, candlestick_phase: CandlestickPhase, price: float, date_time: datetime
    ) -> List[Order]:
        orders: List[Order] = []

        if self.__rsi_indicator[-1] < 30 and self.__rsi_indicator[0] >= 30:
            # Open a long position when RSI crosses above 30
            # and close short position if exists

            if (
                len(self._positions) != 0
                and self._positions[0].position_type == PositionType.SHORT
            ):
                # Close short position if exists
                orders.append(
                    CloseOrder(
                        size=self._positions[0].size,
                        position_type=PositionType.SHORT,
                    )
                )

            if (
                len(self._positions) == 0
                or self._positions[0].position_type != PositionType.LONG
            ):
                orders.append(
                    OpenOrder(
                        size=1,
                        position_type=PositionType.LONG,
                    )
                )

        elif self.__rsi_indicator[-1] > 70 and self.__rsi_indicator[0] <= 70:
            # Open a short position when RSI crosses below 70
            # and close long position if exists

            if (
                len(self._positions) != 0
                and self._positions[0].position_type == PositionType.LONG
            ):
                # Close long position if exists
                orders.append(
                    CloseOrder(
                        size=self._positions[0].size,
                        position_type=PositionType.LONG,
                    )
                )

            if (
                len(self._positions) == 0
                or self._positions[0].position_type != PositionType.SHORT
            ):
                orders.append(
                    OpenOrder(
                        size=1,
                        position_type=PositionType.SHORT,
                    )
                )

        return orders

Declare Backtester

For example purposes, we assume 1% broker's commission and no spread.

commission = Commission(CommissionType.RELATIVE, 0.001)
backtest = Backtester(
    data, RsiStrategy, money=10000, benchmark=data, commission=commission
)
backtest.run()

Print statistics and draw plot

stats = backtest.get_statistics()
print(stats)

plotting = backtest.get_plotting()
plotting.show_plot()
=== Statistics ===
Total trades: 41
Total open trades: 21
Total close trades: 20
Total open long trades: 11
Total close long trades: 10
Total open short trades: 10
Total close short trades: 10
Final money: 5069.67
Final total equity: 10638.73
Return: 638.73 (6.39%)
Max drawdown: 2012.18 (17.66%)
Max drawdown duration: 763
Winning trades: 15 (75.00%)
Best trade return: 20.38%
Worst trade return: -34.94%
Beta: 0.10
Alpha: -0.11
Buy and hold return: 166.80%
Total commission paid: 134.66

RSI Indicator example result Plot

Metadata

Release files for trading-backtester 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for trading-backtester 1.0.0
File Size Uploaded Provenance
trading_backtester-1.0.0.tar.gz 23.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for trading-backtester 1.0.0
File Interpreter ABI Platform
trading_backtester-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 51.0 kB

Release files / trading_backtester-1.0.0.tar.gz

Download URL trading_backtester-1.0.0.tar.gz
Size 23.7 kB
Tags Source
SHA-256 checksum
How to use checksums
0df3d7421814751187bdb30a018a54fc75bbe341957fb3d5e8ec99b5d6faf1b8
BLAKE2b-256 checksum
How to use checksums
63537941d231dae91f679e5888781fe23f39373d8363a93681129a64673ebb28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.3

Release files / trading_backtester-1.0.0-py3-none-any.whl

Download URL trading_backtester-1.0.0-py3-none-any.whl
Size 27.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3a030c99800feea6048349a88c63f4cdb9ef5c5a7901919db017dc4ef12fdd76
BLAKE2b-256 checksum
How to use checksums
4565feb5f0f03a6c15f3bd90c4bd0ced56da08fb1353f6cdaba8065ab9f994d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.3

Security for trading-backtester 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page