https://github.com/joshcap20/pybacktest
https://github.com/joshcap20/pybacktest
Last synced: 8 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/joshcap20/pybacktest
- Owner: JoshCap20
- Created: 2024-10-26T08:54:18.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-28T08:50:40.000Z (over 1 year ago)
- Last Synced: 2025-06-11T05:44:52.094Z (about 1 year ago)
- Language: Python
- Size: 65.4 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# PyBacktest
PyBacktest is a simple and intuitive backtesting framework for Python. It aims to reduce the complexity often associated with backtesting libraries, allowing you to focus on developing and testing trading strategies with ease.
## Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Key Concepts](#key-concepts)
- [Indicators](#indicators)
- [Predicates](#predicates)
- [Strategies](#strategies)
- [Actions](#actions)
- [API Reference](#api-reference)
- [DataFeed](#datafeed)
- [Indicators](#indicators-api)
- [Predicates](#predicates-api)
- [Strategies](#strategies-api)
- [Actions](#actions-api)
- [Backtest](#backtest)
- [Portfolio](#portfolio)
- [Examples](#examples)
- [Indicator Demo: Feature Engineering](#indicator-demo-feature-engineering)
- [Strategy Demo: SMA EMA Crossover](#strategy-demo-sma-ema-crossover)
- [Advanced Usage](#advanced-usage)
- [Creating Custom Indicators](#creating-custom-indicators)
- [Extending Strategies](#extending-strategies)
- [Contributing](#contributing)
- [License](#license)
## Overview
Current backtesting frameworks are often overly complex and difficult to use. PyBacktest simplifies the process by providing:
- A modular structure with clear components.
- An intuitive API for defining indicators, predicates, strategies, and actions.
- Easy integration with data sources like Yahoo Finance.
- Flexibility to extend and customize components according to your needs.
Every backtest run automatically outputs to the run directory all of the data used, trades made, and other relevant information, including graphs of stocks with indicators overlayed. This makes it easy to analyze and visualize the results of your backtest.
## Features
- **Simple API**: Minimal boilerplate code to set up and run backtests.
- **Modular Design**: Components like indicators and strategies are modular and reusable.
- **Extensibility**: Easily create custom indicators and strategies.
- **Data Integration**: Built-in support for fetching data from Yahoo Finance using `yfinance`.
- **Real-Time Simulation**: Step through data points to simulate real-time trading decisions.
## Installation
Install PyBacktest using pip:
```bash
pip install pybacktest
```
## Quick Start
Here's how you can get started with PyBacktest in just a few steps.
```python
from pybacktest import Backtest, YahooFinanceDataFeed
from pybacktest.indicators import SMAIndicator, EMAIndicator
from pybacktest.predicates import Predicate
from pybacktest.strategies import Strategy
from pybacktest.actions import BuyAction, SellAction
import operator
# Define symbols and fetch data
symbols = ["AAPL", "MSFT", "GOOGL"]
data_feed = YahooFinanceDataFeed(symbols, start="2021-01-01", end="2021-12-31")
# Add indicators
sma_indicator = SMAIndicator(window=14)
ema_indicator = EMAIndicator(window=25)
data_feed.add_indicators([sma_indicator, ema_indicator])
# Define predicates
sma_below_ema = Predicate(sma_indicator, operator.lt, ema_indicator)
sma_above_ema = Predicate(sma_indicator, operator.gt, ema_indicator)
# Define actions
buy_action = BuyAction()
sell_action = SellAction()
# Define strategy
strategy = Strategy(
entry_conditions=[sma_below_ema],
entry_action=buy_action,
exit_conditions=[sma_above_ema],
exit_action=sell_action,
)
# Subscribe strategy to data feed
data_feed.subscribe(strategy)
# Create and run backtest
backtest = Backtest(data_feed, initial_balance=10000)
backtest.run()
```
## Key Concepts
### Indicators
Indicators transform data and add new columns to the DataFrame for each stock. They are akin to feature engineering in machine learning. When an indicator is added, it mutates the DataFrame to include its own columns.
Examples of indicators:
- SMA (Simple Moving Average)
- EMA (Exponential Moving Average)
- RSI (Relative Strength Index)
- MACD (Moving Average Convergence Divergence)
### Predicates
Predicates use indicators, comparison operators, and values to return a boolean. They form the logical conditions for strategies.
Example Predicate:
```python
from pybacktest.strategies import Predicate
import operator
sma_below_ema = Predicate(
input1=sma_indicator,
operator=operator.lt,
input2=ema_indicator
)
```
### Strategies
Strategies are composed of one or more predicates with actions to take when they evaluate to True. They define the entry and exit points of your trading algorithm.
Example Strategy:
```python
from pybacktest.strategies import Strategy
strategy = Strategy(
entry_conditions=[sma_below_ema],
entry_action=buy_action,
exit_conditions=[sma_above_ema],
exit_action=sell_action,
)
```
### Actions
Actions are what happens when predicates evaluate to True in a strategy. Common actions include buying or selling assets.
Available Actions:
- BuyAction
- SellAction
## API Reference
### DataFeed
The `DataFeed` class handles the data input for the backtest. It manages the data retrieval, indicator application, and strategy subscriptions.
YahooFinanceDataFeed Example:
```python
from pybacktest.data.yahoo_finance_data_feed import YahooFinanceDataFeed
data_feed = YahooFinanceDataFeed(symbols=["AAPL"], start="2021-01-01", end="2021-12-31")
```
Methods:
- `add_indicators(indicators)`: Adds indicators to the data feed.
- `subscribe(strategy)`: Subscribes a strategy to the data feed.
### Indicators API
Indicators calculate specific metrics based on historical data and add them to the DataFrame.
Base Indicator Class:
```python
from pybacktest.indicators import Indicator
class Indicator:
def apply(self, data: pd.DataFrame) -> None:
pass
```
Built-in Indicators:
- `SMAIndicator(window)`
- `EMAIndicator(window)`
- `RSIIndicator(window)`
- `MACDIndicator()`
- `BollingerBandsIndicator(window)`
- `ATRIndicator(window)`
- `ADXIndicator(window)`
- `OBVIndicator()`
Adding Indicators:
```python
from pybacktest.indicators import SMAIndicator, EMAIndicator
sma_indicator = SMAIndicator(window=14)
ema_indicator = EMAIndicator(window=25)
data_feed.add_indicators([sma_indicator, ema_indicator])
```
### Predicates API
Predicates evaluate conditions based on indicators or specific values. This should be intuitive: in order to take an action, whether buy or sell, there must be some definite condition to encapsulate that logic. Predicates are the way to do that.
Predicate Class:
```python
from pybacktest.strategies import Predicate
predicate = Predicate(
input1=indicator_or_value,
operator=comparison_operator,
input2=indicator_or_value
)
```
Operators:
- `operator.lt` (less than)
- `operator.gt` (greater than)
- `operator.eq` (equal to)
- `operator.ne` (not equal)
- `operator.le` (less than or equal to)
- `operator.ge` (greater than or equal to)
Example:
```python
import operator
rsi_below_70 = Predicate(
input1=rsi_indicator,
operator=operator.lt,
input2=70 # Comparing RSI value to 70
)
vwamp_above_price = Predicate(
input1=IndicatorInput(vwamp_indicator, column_name=vwamp_indicator.indicator_name),
operator=operator.gt,
input2="Adj Close",
)
sma_above_ema = Predicate(
input1=sma_indicator,
operator=operator.gt,
input2=ema_indicator,
)
```
#### Predicate Extension via Bitwise Operators
To make using predicates even easier, you can use the following bitwise operators to extend the functionality of the `Predicate` class:
- `&` (and)
- `|` (or)
- `~` (not)
```python
# Example of combining predicates
combined_predicate = rsi_below_70 & sma_above_ema
# Example of negating a predicate
rsi_above_70 = ~rsi_below_70
```
**These are brand new and in testing stages. Please report any issues you may encounter.**
### Strategies API
Strategies combine predicates and actions to define trading logic.
Strategy Class:
```python
from pybacktest.strategies import Strategy
strategy = Strategy(
entry_conditions=[predicate1, predicate2],
entry_action=buy_action,
exit_conditions=[predicate3],
exit_action=sell_action,
)
```
Methods:
- `apply(data, context)`: Applies the strategy to the data at each time step.
### Actions API
Actions define what happens when a strategy's conditions are met.
Action Base Class:
```python
from pybacktest.actions import Action
class Action:
def execute(self, data: pd.Series, context: dict) -> None:
pass
```
Available Actions:
- `BuyAction()`
- `SellAction()`
Using Actions:
```python
from pybacktest.actions import BuyAction, SellAction
buy_action = BuyAction()
sell_action = SellAction()
```
### Backtest
The `Backtest` class orchestrates the execution of the backtest.
Creating a Backtest:
```python
from pybacktest import Backtest
backtest = Backtest(data_feed, initial_balance=10000)
```
Running the Backtest:
```python
backtest.run()
```
### Portfolio
The `Portfolio` class manages cash, positions, and transaction history.
Portfolio Attributes:
- `cash`: Current cash balance.
- `positions`: Dictionary of symbol to quantity held.
- `transaction_history`: List of transactions executed.
Portfolio Methods:
- `buy(symbol, quantity, price, timestamp)`: Buys a specified quantity of a symbol.
- `sell(symbol, quantity, price, timestamp)`: Sells a specified quantity of a symbol.
- `total_portfolio_value(current_prices)`: Calculates the total value of the portfolio.
## Examples
### Indicator Demo: Feature Engineering
This demo retrieves data for specified stocks and adds SMA and EMA indicators to the DataFrame.
```python
from pybacktest import YahooFinanceDataFeed
from pybacktest.indicators import SMAIndicator, EMAIndicator
symbols = ["AAPL", "MSFT", "GOOGL"]
# Initialize data feed
data_feed = YahooFinanceDataFeed(symbols, start="2021-01-01", end="2021-12-31")
# Define indicators
sma_indicator = SMAIndicator(window=50)
ema_indicator = EMAIndicator(window=20)
# Add indicators to data feed
data_feed.add_indicators([sma_indicator, ema_indicator])
# Display the modified DataFrame
print(data_feed._data.tail())
```
### Strategy Demo: SMA EMA Crossover
This strategy buys when the SMA crosses below the EMA and sells when the SMA crosses above the EMA.
```python
from pybacktest import Backtest, YahooFinanceDataFeed
from pybacktest.indicators import SMAIndicator, EMAIndicator
from pybacktest.strategies import Predicate, Strategy
from pybacktest.actions import BuyAction, SellAction
import operator
# Fetch data
symbols = ["AAPL", "MSFT", "GOOGL"]
data_feed = YahooFinanceDataFeed(symbols, start="2020-01-01", end="2020-12-01")
# Add indicators
sma_indicator = SMAIndicator(window=14)
ema_indicator = EMAIndicator(window=25)
data_feed.add_indicators([sma_indicator, ema_indicator])
# Define predicates
sma_below_ema = Predicate(
input1=sma_indicator,
operator=operator.lt,
input2=ema_indicator
)
sma_above_ema = Predicate(
input1=sma_indicator,
operator=operator.gt,
input2=ema_indicator
)
# Define actions
buy_action = BuyAction()
sell_action = SellAction()
# Define strategy
strategy = Strategy(
entry_conditions=[sma_below_ema],
entry_action=buy_action,
exit_conditions=[sma_above_ema],
exit_action=sell_action,
)
# Subscribe strategy to data feed
data_feed.subscribe(strategy)
# Create and run backtest
backtest = Backtest(data_feed, initial_balance=10000)
backtest.run()
```
### Strategy Demo: VWAMP and Price Crossover
This strategy buys when the SMA crosses below the EMA and sells when the SMA crosses above the EMA.
```python
from pybacktest import Backtest, YahooFinanceDataFeed
from pybacktest.indicators import VWAPIndicator
from pybacktest.strategies import Predicate, Strategy, IndicatorInput
from pybacktest.actions import BuyAction, SellAction
from pybacktest.data.stock_groups import SPY_500
import operator
# Fetch data
symbols = SPY_500
data_feed = YahooFinanceDataFeed(symbols, start="2020-01-01", end="2020-12-01")
# Add indicators
vwamp_indicator = VWAPIndicator(column="Adj Close")
data_feed.add_indicators([vwamp_indicator])
# Define predicates
# Note: When an indicator has multiple column names, one must be explicitly specified via an IndicatorInput object
vwamp_above_price = Predicate(
input1=IndicatorInput(vwamp_indicator, column_name=vwamp_indicator.indicator_name),
operator=operator.gt,
input2="Adj Close",
)
# We can use the overridden operators to negate predicates
vwamp_below_price = ~vwamp_above_price
# Define actions
buy_action = BuyAction()
sell_action = SellAction()
# Define strategy
strategy = Strategy(
entry_conditions=[vwamp_above_price],
entry_action=buy_action,
exit_conditions=[vwamp_below_price],
exit_action=sell_action,
)
# Subscribe strategy to data feed
data_feed.subscribe(strategy)
# Create and run backtest
backtest = Backtest(data_feed, initial_balance=10000)
backtest.run()
```
## Advanced Usage
### Creating Custom Indicators
You can extend the `Indicator` base class to create custom indicators.
```python
from pybacktest.indicators import Indicator
class CustomIndicator(Indicator):
def __init__(self, window, column: str = "Close"):
self.window = windows
self.column = column
self.column_names = [f"Custom_{window}"]
def apply(self, data: pd.DataFrame) -> None:
for symbol in data.columns.get_level_values(0).unique():
data[(symbol, self.column_names[0])] = (
data[(symbol, self.column)].rolling(window=self.window).mean()
)
```
Usage:
```python
custom_indicator = CustomIndicator(window=10)
data_feed.add_indicators([custom_indicator])
```
### Extending Strategies
You can create more complex strategies by combining multiple predicates and actions.
Example:
```python
from pybacktest.indicators import RSIIndicator
from pybacktest.predicates import Predicate
import operator
# Add RSI indicator
rsi_indicator = RSIIndicator(window=14)
data_feed.add_indicators([rsi_indicator])
# Define additional predicates
rsi_below_30 = Predicate(
input1=rsi_indicator,
operator=operator.lt,
input2=30
)
# Update strategy
strategy = Strategy(
entry_conditions=[sma_below_ema, rsi_below_30],
entry_action=buy_action,
exit_conditions=[sma_above_ema],
exit_action=sell_action,
)
```
## Contributing
Contributions are welcome! If you'd like to contribute to PyBacktest, please follow these steps:
1. Fork the repository.
2. Create a new branch for your feature or bugfix.
3. Submit a pull request with a detailed description of your changes.