Skip to main content

Framework for backtesting algorithmic trading strategies

Project description

Nara Backtesting Platform

The Nara Backtesting Platform is a comprehensive framework designed for backtesting algorithmic trading strategies. It provides a robust set of tools to help you develop, test, and visualize your trading strategies. This package includes:

  • A base class for defining strategies.
  • A strategy manager to handle multiple strategies.
  • A backtest engine to simulate trading based on historical data.
  • Visualization tools to analyze the performance of your strategies.

Features

  • Strategy Definition: Define your own trading strategies by extending the base Strategy class.
  • Strategy Management: Manage multiple strategies simultaneously using the StrategyManager.
  • Backtesting: Run backtests to evaluate the performance of your strategies on historical data.
  • Visualization: Generate plots to visualize prices, positions, trades, and PnL for each strategy.
  • Metrics Calculation: Calculate performance metrics such as PnL, volatility, max drawdown, and Sharpe ratio.
  • Export Results: Export backtest results to an Excel file for further analysis.

Installation

To use the Nara Backtesting Platform, you need to install the required package using pip:

pip install backtesting-package

Usage

Define Your Strategies

To define a new strategy, extend the Strategy base class and implement the calculate_signal and calculate_positions methods. Here is an example of a simple moving average strategy:

from backtest import Strategy

class MovingAverageStrategy(Strategy):
    def __init__(self, name, tickers, short_window, long_window):
        """
        Initialize the MovingAverageStrategy with specified parameters.

        Parameters:
        name (str): The name of the strategy.
        tickers (list): List of ticker symbols.
        short_window (int): The window size for the short moving average.
        long_window (int): The window size for the long moving average.
        """
        super().__init__(name)
        self.tickers = tickers
        self.short_window = short_window
        self.long_window = long_window

    def calculate_signal(self):
        """
        Calculate the signals for each ticker based on moving averages.
        """
        for ticker in self.tickers:
            # Calculate short and long moving averages
            self.data[f'{self.strategy_name}_{ticker}_short_ma'] = self.data[ticker].rolling(window=self.short_window).mean()
            self.data[f'{self.strategy_name}_{ticker}_long_ma'] = self.data[ticker].rolling(window=self.long_window).mean()
            self.data[f'{self.strategy_name}_{ticker}_signal'] = 0.0

            # Generate signals where the short MA is greater than the long MA
            valid_index = self.data.index[self.short_window:]  # Valid index for slicing
            self.data.loc[valid_index, f'{self.strategy_name}_{ticker}_signal'] = np.where(
                self.data.loc[valid_index, f'{self.strategy_name}_{ticker}_short_ma'] > self.data.loc[valid_index, f'{self.strategy_name}_{ticker}_long_ma'], 1.0, 0.0
            )

    def calculate_positions(self):
        """
        Calculate positions based on the signals.

        Returns:
        pd.DataFrame: DataFrame containing the positions for each ticker at the last date.
        """
        positions_list = []
        last_date = self.data.index[-1]
        for ticker in self.tickers:
            positions = pd.DataFrame({
                'time': [last_date],
                'book': self.strategy_name,
                'ticker': ticker,
                'units': np.where(self.data.loc[last_date, f'{self.strategy_name}_{ticker}_signal'] == 1.0, 10, -10)
            })
            positions_list.append(positions)
        return pd.concat(positions_list)

Signals

Instead of calculating your signals at each iteration within the strategy, you can compute them at the beginning and use them as needed throughout your strategy. Please note that your signals will be available to your strategy only up to the current date of iteration. Ensure that you do not compute them using future data (e.g., fitting a regression on the entire dataset and then iterating over it as if it were a signal).

Define your signals

# Calculate moving averages and signals
signals = pd.DataFrame(index=data.index)

for ticker in tickers:
    signals[f'StrategyShortMA_{ticker}_short_ma'] = data[ticker].rolling(window=5).mean()
    signals[f'StrategyShortMA_{ticker}_long_ma'] = data[ticker].rolling(window=21).mean()
    signals[f'StrategyLongMA_{ticker}_short_ma'] = data[ticker].rolling(window=20).mean()
    signals[f'StrategyLongMA_{ticker}_long_ma'] = data[ticker].rolling(window=50).mean()

# Display
signals.plot()

Define your strategy

from backtest import Strategy

class MovingAverageStrategy(Strategy):
    def __init__(self, name, tickers, short_window, long_window):
        """
        Initialize the MovingAverageStrategy with specified parameters.

        Parameters:
        name (str): The name of the strategy.
        tickers (list): List of ticker symbols.
        short_window (int): The window size for the short moving average.
        long_window (int): The window size for the long moving average.
        """
        super().__init__(name)
        self.tickers = tickers
        self.short_window = short_window
        self.long_window = long_window

    def calculate_signal(self):
        """
        Calculate the signals for each ticker using precomputed moving averages from self.signals.
        """
        for ticker in self.tickers:
            short_ma_column = f'{self.strategy_name}_{ticker}_short_ma'
            long_ma_column = f'{self.strategy_name}_{ticker}_long_ma'
            self.data[f'{self.strategy_name}_{ticker}_signal'] = 0.0

            # Generate signals where the short MA is greater than the long MA
            valid_index = self.data.index[self.short_window:]  # Valid index for slicing
            self.data.loc[valid_index, f'{self.strategy_name}_{ticker}_signal'] = np.where(
                self.signal.loc[valid_index, short_ma_column] > self.signal.loc[valid_index, long_ma_column], 1.0, 0.0
            )

    def calculate_positions(self):
        """
        Calculate positions based on the signals.

        Returns:
        pd.DataFrame: DataFrame containing the positions for each ticker.
        """
        positions_list = []
        for ticker in self.tickers:
            positions = pd.DataFrame({
                'time': self.data.index,
                'book': self.strategy_name,
                'ticker': ticker,
                'units': np.where(self.data[f'{self.strategy_name}_{ticker}_signal'] == 1.0, 10, -10)
            })
            positions_list.append(positions)
        return pd.concat(positions_list)

Fetch and Format Your Data

Ensure your price data is formatted correctly. The data should be a pandas DataFrame with tickers as columns and a datetime index. Here is an example of how to format your data:

import pandas as pd

# Define tickers and date range
tickers = ['AAPL', 'LMT']
start_date = '2010-01-01'
end_date = '2025-01-01'

# Fetch data (replace this with your data fetching logic)
data = pd.DataFrame({
    'AAPL': np.random.randn(2500) + 100,
    'LMT': np.random.randn(2500) + 200
}, index=pd.date_range(start=start_date, periods=2500))

# Ensure the columns are well named
data.columns = tickers

# Drop rows with missing values
data = data.dropna()

# Convert index to datetime format
data.index = pd.to_datetime(data.index)

# Sort the data by index
data = data.sort_index()

Initialize and Run Backtest

Initialize the backtest with your strategies and run it:

from backtest import Backtest, DisplayBacktest

# Initialize strategies
strategy1 = MovingAverageStrategy(name="StrategyShortMA", data=data, short_window=5, long_window=21)
strategy2 = MovingAverageStrategy(name="StrategyLongMA", data=data, short_window=20, long_window=50)
strategies = [strategy1, strategy2]
weights = {"StrategyShortMA": 0.7, "StrategyLongMA": 0.3}

# Run backtest
backtest = Backtest(data, strategies, weights)

Visualize Results

Use the DisplayBacktest class to visualize the results of your backtest:

# Plot results
display = DisplayBacktest(backtest)
display.plot_book("StrategyShortMA")
display.plot_cumulative_pnl_per_book()
display.plot_cumulative_pnl()
display.plot_individual_pnl()
display.plot_pnl_distribution()
display.plot_signals()  # New method to plot signals if defined at the beginning

Calculate Metrics

Calculate performance metrics for your strategies:

# Get yearly and monthly metrics
yearly_metrics = display.get_metrics(book='StrategyShortMA', resample_period='Y')  # Yearly metrics
monthly_metrics = display.get_metrics(book='StrategyShortMA', resample_period='M')  # Monthly metrics

Export Results

Export the backtest results to an Excel file for further analysis:

# Export excel
backtest.export_excel("backtest_results.xlsx")

Conclusion

The Nara Backtesting Platform provides a powerful and flexible framework for backtesting algorithmic trading strategies. With its comprehensive set of tools, you can define, manage, and evaluate your strategies with ease. Whether you are a seasoned trader or a beginner, this platform offers everything you need to develop and test your trading strategies effectively.

Upcoming Features and Improvements

We are continuously working to enhance the functionality and usability of the backtesting_package. In the upcoming releases, you can expect the following improvements:

  • [implemented in 1.2] Signal Visualization and Export: We plan to introduce features that allow you to visualize trading signals and export them to Excel for further analysis.
  • Addition of Options: We will be adding support for options trading, providing more flexibility for your backtesting strategies.
  • Enhanced Display Class: The Display class will be improved to offer better visualization and user experience, making it easier to interpret backtesting results.

Stay tuned for these exciting updates!

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

backtesting-package-1.2.tar.gz (229.6 kB view details)

Uploaded Source

Built Distribution

backtesting_package-1.2-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

Details for the file backtesting-package-1.2.tar.gz.

File metadata

  • Download URL: backtesting-package-1.2.tar.gz
  • Upload date:
  • Size: 229.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.8.10

File hashes

Hashes for backtesting-package-1.2.tar.gz
Algorithm Hash digest
SHA256 9b2b3f89b6becb4fa5e581c1bb44adfd065bc48fa793c9bcdb9a17baa0dc0882
MD5 8273d5c4aaaa5b434cb92a425e020f77
BLAKE2b-256 e75bdb27cf354d658b9b86db958dcbb57ec65deb76a85d014d0528a97dbcc3bd

See more details on using hashes here.

File details

Details for the file backtesting_package-1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for backtesting_package-1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 00e2d67470ec1dc9be78da201a7be8b0255a430c47561fe0ad0cdba73c8db158
MD5 01387b2c1c6e98a20d350e7a169558ae
BLAKE2b-256 cc2a740e9cf5484af17c27c4fd3c6027d3e5691c95937117bd5f6c9892934053

See more details on using hashes here.

Supported by

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