simple-backtester


Namesimple-backtester JSON
Version 0.1.1 PyPI version JSON
download
home_pageNone
SummarySimple, no frills backtesting
upload_time2024-10-31 14:42:33
maintainerNone
docs_urlNone
authorShida SB
requires_python>=3.9
licenseMIT
keywords backtesting
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Simple Backtesting in Python

![License](https://img.shields.io/pypi/l/simple-backtester)
![Python versions](https://img.shields.io/pypi/pyversions/simple-backtester)

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

> Note that this project is in <ins>**active development**</ins>. For feature suggestions, feel free to head on over to the [Issues](https://github.com/SSBakh07/simple-backtester/issues) tab.

Compatible with Python 3.9+.

## Dependencies

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

## Install

You can install from [PyPI](https://pypi.org/project/simple-backtester/):

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

```python
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. `Order`s 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:

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

or for sell orders:

```python
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:

```python
# 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:

```python
# 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:

```python
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:

```python
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`](https://pandas.pydata.org/docs/reference/frame.html). I'll be adding alternative data sources in the near future.

> Tick data is not supported at this time.

```python
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:

```python
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!

```python
test_strat.start()
```

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

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

And we can fetch our win-rate like so:

```python
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:

```python
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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "simple-backtester",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "backtesting",
    "author": "Shida SB",
    "author_email": "ssbakh07@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/5f/07/dbe5ae060bd41573779406386c5331597779e600df75ea969de0a1ab7102/simple_backtester-0.1.1.tar.gz",
    "platform": null,
    "description": "# Simple Backtesting in Python\n\n![License](https://img.shields.io/pypi/l/simple-backtester)\n![Python versions](https://img.shields.io/pypi/pyversions/simple-backtester)\n\nA simple backtesting library in Python with no frills and all the customization. Just easy backtesting.\n\n> Note that this project is in <ins>**active development**</ins>. For feature suggestions, feel free to head on over to the [Issues](https://github.com/SSBakh07/simple-backtester/issues) tab.\n\nCompatible with Python 3.9+.\n\n## Dependencies\n\n- Numpy (1.26.0, <2.0.0)\n- Pandas\n- tqdm\n\n## Install\n\nYou can install from [PyPI](https://pypi.org/project/simple-backtester/):\n\n```\npip install simple-backtester\n```\n\nOr you can install directly from the repo:\n\n```\ngit clone https://github.com/SSBakh07/simple-backtester\ncd simple-backtester\npip install .\n```\n\n## Usage guide\n\nPretend 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.\n\n### 1. Implement your strategy\n\nWe 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.\n\n```python\nfrom simple_backtester import SBStrat\n\nclass SimpleStrat(SBStrat):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.bullish = False\n        self.bearish = False\n        self.last_price = None\n    \n    def on_next(self, new_row: pd.Series):\n        # Fetch the latest close price\n        new_close = new_row[self.close_col]\n        \n        # If last_price hasn't been set, let's do that first!\n        if not self.last_price:\n            self.last_price = new_close\n            return\n        \n        # Compare our newest close price with our last close price\n        if self.last_price >= new_close:\n            if self.bullish:\n                self.sell()  # Spot order\n                self.bullish = False\n            else:\n                self.bullish = True\n                self.bearish = False\n        \n        else:\n            if self.bearish:\n                self.buy()  # Spot order\n                self.bearish = False\n            else:\n                self.bearish = True\n                self.bullish = False\n        \n        self.last_price = new_close\n```\n\n\nWhat happens whenever `buy` or `sell` is called, an `Order` object is made. `Order`s can have one of the following statuses:\n- `SUBMITTED`\n- `OPEN`\n- `EXECUTED_LOSS` (I.E. our position was closed at stop loss)\n- `EXECUTED_PROFIT` (I.E. our position was closed at take profit)\n- `EXPIRED` (I.E. order expired before it could be opened)\n\n> Note that at the moment, only market orders have been implemented. In future versions limit orders will be added\n\nWe can set prices for our market order if we so wish:\n\n```python\nself.buy(\n    buy_stop = 1000.5   # Open buy order when we hit 1000.5\n)\n```\n\nor for sell orders:\n\n```python\nself.sell(\n    sell_stop = 1000.5   # Open sell order when we hit 1000.5\n)\n```\n\n\nWe can also set stop losses and take profits as needed:\n\n```python\n# Set stop loss at 900, and take profit at 1100\nself.buy(\n    buy_stop = 1000.5,\n    take_profit = 1100,\n    stop_loss = 900\n)\n```\n\nOr even set an expiration datetime for our buy and sell orders:\n\n```python\n# Set stop loss at 900, and take profit at 1100\nself.sell(\n    sell_stop = 900,\n    take_profit = 800,\n    stop_loss = 1150,\n    expiry_time = \"2024-01-01\"  # Can be str or pd.Timestamp\n)\n```\n\n\n\nIf we want to do something every time an order's status changes (such as logging), we can implement the `on_order_change` abstract method:\n\n```python\nimport logging\nfrom simple_backtester import SBStrat, Order, ORDER_STATUS\n\n\nclass SimpleStrat(SBStrat):\n    def __init__(self, **kwargs):\n        ...\n    \n    def on_next(self, new_row: pd.Series):\n        ...\n\n    def on_order_change(self, order: Order):\n        if order.status == ORDER_STATUS.OPEN:\n            logging.info(f\"Order opened! {order.order_type}\")\n        if order.status == sbs.ORDER_STATUS.EXECUTED_LOSS or order.status == sbs.ORDER_STATUS.EXECUTED_PROFIT:\n            logging.info(f\"Order closed! Current portfolio value: {self.portfolio}\")\n        if order.status == sbs.ORDER_STATUS.EXPIRED:\n            logging.info(\"Order expired!\")\n```\n\n\n\nAnd then, all we need to do is to create an instance of our strategy class:\n\n```python\ntest_strat = SimpleStrat(balance = 15000)    # Start with 15000 units. Defaults to 1000\n```\n\n\n### 2. Add your data\n\nFor now, the only way we can add our candle data data is by passing in a [`pandas DataFrame`](https://pandas.pydata.org/docs/reference/frame.html). I'll be adding alternative data sources in the near future.\n\n> Tick data is not supported at this time.\n\n```python\nimport pandas as pd\n\nohlc_data = pd.read_csv(\"example_candle_data.csv\")\ntest_strat.add_data(ohlc_data)\n```\n\n\nIf our columns have different names or our date column has a specific format, we can pass those in:\n\n```python\ntest_strat.add_data(\n        ohlc_data,\n        open_col = \"OPEN\",  # Defaults to \"open\"\n        high_col = \"HIGH\",  # Defaults to \"high\"\n        low_col = \"LOW\",    # Defaults to \"low\"\n        close_col = \"CLOSE\",    # Defaults to \"close\"\n        date_col = \"DATETIME\",  # Defaults to \"date\"\n        date_fmt = \"%d-%m-%Y %H:%M:%S\"  # Infers by default\n    )\n```\n\n\n### 3. Profit!\n\nAnd now, all that's left is running our test!\n\n```python\ntest_strat.start()\n```\n\nWe can specify our tests to be run from or to a certain date:\n\n```python\ntest_strat.start(\n    start_date = \"01-01-2023\",\n    end_date = \"01-01-2024\"\n)\n```\n\nAnd we can fetch our win-rate like so:\n\n```python\ntest_strat.win_rate\n```\n\nAnd if we want to start over and rerun our tests, all we need to do is call the `reset()` function:\n\n```python\ntest_strat.reset()\n```\n\nIt's *that* simple! :)\n\n\n## To-do List:\n\n+ [ ] Limit orders\n+ [ ] Add risk/reward ratio\n+ [ ] Adding support for Python3.8 and Python3.7\n+ [ ] Writing up *proper* documentation\n+ [ ] Adding more tests/cleaning up tests\n+ [ ] Adding indicators using `talib`\n+ [ ] Adding commission fees\n+ [ ] Adding Yfinance support\n+ [ ] Adding support for tick data\n\n\n> Questions? Concerns? Email me at ssbakh07 (at) gmail.com\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Simple, no frills backtesting",
    "version": "0.1.1",
    "project_urls": null,
    "split_keywords": [
        "backtesting"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b8d6cbb207f286d20d34218432e500a4704c42984f0ca7e8e7993039a963c9a0",
                "md5": "2714c6b2277a7d576e69df88bf69eb55",
                "sha256": "72c28d8d9223582910ec37cc70e576935b9fc4c9c2449d5ec9b2a087b16e879e"
            },
            "downloads": -1,
            "filename": "simple_backtester-0.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2714c6b2277a7d576e69df88bf69eb55",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 9944,
            "upload_time": "2024-10-31T14:42:31",
            "upload_time_iso_8601": "2024-10-31T14:42:31.529206Z",
            "url": "https://files.pythonhosted.org/packages/b8/d6/cbb207f286d20d34218432e500a4704c42984f0ca7e8e7993039a963c9a0/simple_backtester-0.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5f07dbe5ae060bd41573779406386c5331597779e600df75ea969de0a1ab7102",
                "md5": "79ef192d13a5b4e7b004e224aca16dbf",
                "sha256": "0764225621e01322423053d1a9f03644c7a6b4ce66d05c8464f2b0f824669de7"
            },
            "downloads": -1,
            "filename": "simple_backtester-0.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "79ef192d13a5b4e7b004e224aca16dbf",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 10263,
            "upload_time": "2024-10-31T14:42:33",
            "upload_time_iso_8601": "2024-10-31T14:42:33.042660Z",
            "url": "https://files.pythonhosted.org/packages/5f/07/dbe5ae060bd41573779406386c5331597779e600df75ea969de0a1ab7102/simple_backtester-0.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-31 14:42:33",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "simple-backtester"
}
        
Elapsed time: 0.94543s