Skip to main content

Simple, no frills backtesting

Project description

Simple Backtesting in Python

License Python versions

A simple backtesting library in Python with no frills and all the customization. Just easy backtesting.

Note that this project is in active development. For feature suggestions, feel free to head on over to the Issues tab.

Compatible with Python 3.9+.

Dependencies

  • Numpy (1.26.0, <2.0.0)
  • Pandas
  • tqdm

Install

You can install from PyPI:

pip install simple-backtester

Or you can install directly from the repo:

git clone https://github.com/SSBakh07/simple-backtester
cd simple-backtester
pip install .

Usage guide

Pretend we're looking to implement a simple strategy where if the closing price falls twice, we buy. If the price rises two candles in a row, we sell.

1. Implement your strategy

We do this by creating a class that inherits from the SBStrat subclass. All we need to do is implement the on_next method which only takes one argument: our latest data.

from simple_backtester import SBStrat

class SimpleStrat(SBStrat):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bullish = False
        self.bearish = False
        self.last_price = None
    
    def on_next(self, new_row: pd.Series):
        # Fetch the latest close price
        new_close = new_row[self.close_col]
        
        # If last_price hasn't been set, let's do that first!
        if not self.last_price:
            self.last_price = new_close
            return
        
        # Compare our newest close price with our last close price
        if self.last_price >= new_close:
            if self.bullish:
                self.sell()  # Spot order
                self.bullish = False
            else:
                self.bullish = True
                self.bearish = False
        
        else:
            if self.bearish:
                self.buy()  # Spot order
                self.bearish = False
            else:
                self.bearish = True
                self.bullish = False
        
        self.last_price = new_close

What happens whenever buy or sell is called, an Order object is made. Orders can have one of the following statuses:

  • SUBMITTED
  • OPEN
  • EXECUTED_LOSS (I.E. our position was closed at stop loss)
  • EXECUTED_PROFIT (I.E. our position was closed at take profit)
  • EXPIRED (I.E. order expired before it could be opened)

Note that at the moment, only market orders have been implemented. In future versions limit orders will be added

We can set prices for our market order if we so wish:

self.buy(
    buy_stop = 1000.5   # Open buy order when we hit 1000.5
)

or for sell orders:

self.sell(
    sell_stop = 1000.5   # Open sell order when we hit 1000.5
)

We can also set stop losses and take profits as needed:

# Set stop loss at 900, and take profit at 1100
self.buy(
    buy_stop = 1000.5,
    take_profit = 1100,
    stop_loss = 900
)

Or even set an expiration datetime for our buy and sell orders:

# Set stop loss at 900, and take profit at 1100
self.sell(
    sell_stop = 900,
    take_profit = 800,
    stop_loss = 1150,
    expiry_time = "2024-01-01"  # Can be str or pd.Timestamp
)

If we want to do something every time an order's status changes (such as logging), we can implement the on_order_change abstract method:

import logging
from simple_backtester import SBStrat, Order, ORDER_STATUS


class SimpleStrat(SBStrat):
    def __init__(self, **kwargs):
        ...
    
    def on_next(self, new_row: pd.Series):
        ...

    def on_order_change(self, order: Order):
        if order.status == ORDER_STATUS.OPEN:
            logging.info(f"Order opened! {order.order_type}")
        if order.status == sbs.ORDER_STATUS.EXECUTED_LOSS or order.status == sbs.ORDER_STATUS.EXECUTED_PROFIT:
            logging.info(f"Order closed! Current portfolio value: {self.portfolio}")
        if order.status == sbs.ORDER_STATUS.EXPIRED:
            logging.info("Order expired!")

And then, all we need to do is to create an instance of our strategy class:

test_strat = SimpleStrat(balance = 15000)    # Start with 15000 units. Defaults to 1000

2. Add your data

For now, the only way we can add our candle data data is by passing in a pandas DataFrame. I'll be adding alternative data sources in the near future.

Tick data is not supported at this time.

import pandas as pd

ohlc_data = pd.read_csv("example_candle_data.csv")
test_strat.add_data(ohlc_data)

If our columns have different names or our date column has a specific format, we can pass those in:

test_strat.add_data(
        ohlc_data,
        open_col = "OPEN",  # Defaults to "open"
        high_col = "HIGH",  # Defaults to "high"
        low_col = "LOW",    # Defaults to "low"
        close_col = "CLOSE",    # Defaults to "close"
        date_col = "DATETIME",  # Defaults to "date"
        date_fmt = "%d-%m-%Y %H:%M:%S"  # Infers by default
    )

3. Profit!

And now, all that's left is running our test!

test_strat.start()

We can specify our tests to be run from or to a certain date:

test_strat.start(
    start_date = "01-01-2023",
    end_date = "01-01-2024"
)

And we can fetch our win-rate like so:

test_strat.win_rate

And if we want to start over and rerun our tests, all we need to do is call the reset() function:

test_strat.reset()

It's that simple! :)

To-do List:

  • Limit orders
  • Add risk/reward ratio
  • Adding support for Python3.8 and Python3.7
  • Writing up proper documentation
  • Adding more tests/cleaning up tests
  • Adding indicators using talib
  • Adding commission fees
  • Adding Yfinance support
  • Adding support for tick data

Questions? Concerns? Email me at ssbakh07 (at) gmail.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simple_backtester-0.1.1.tar.gz (10.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simple_backtester-0.1.1-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

Details for the file simple_backtester-0.1.1.tar.gz.

File metadata

  • Download URL: simple_backtester-0.1.1.tar.gz
  • Upload date:
  • Size: 10.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.12.3 Linux/6.8.0-47-generic

File hashes

Hashes for simple_backtester-0.1.1.tar.gz
Algorithm Hash digest
SHA256 0764225621e01322423053d1a9f03644c7a6b4ce66d05c8464f2b0f824669de7
MD5 79ef192d13a5b4e7b004e224aca16dbf
BLAKE2b-256 5f07dbe5ae060bd41573779406386c5331597779e600df75ea969de0a1ab7102

See more details on using hashes here.

File details

Details for the file simple_backtester-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: simple_backtester-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.12.3 Linux/6.8.0-47-generic

File hashes

Hashes for simple_backtester-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 72c28d8d9223582910ec37cc70e576935b9fc4c9c2449d5ec9b2a087b16e879e
MD5 2714c6b2277a7d576e69df88bf69eb55
BLAKE2b-256 b8d6cbb207f286d20d34218432e500a4704c42984f0ca7e8e7993039a963c9a0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page