Skip to main content

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!

Metadata

Release files for backtesting-package 1.2

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

Source distribution (sdist)

Source distribution for backtesting-package 1.2
File Size Uploaded
backtesting-package-1.2.tar.gz 229.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for backtesting-package 1.2
File Interpreter ABI Platform
backtesting_package-1.2-py3-none-any.whl Python 3 none any Details

Total release size: 241.2 kB

Release files / backtesting-package-1.2.tar.gz

Download URL backtesting-package-1.2.tar.gz
Size 229.6 kB
Tags Source
SHA-256 checksum
How to use checksums
9b2b3f89b6becb4fa5e581c1bb44adfd065bc48fa793c9bcdb9a17baa0dc0882
BLAKE2b-256 checksum
How to use checksums
e75bdb27cf354d658b9b86db958dcbb57ec65deb76a85d014d0528a97dbcc3bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.1.1 CPython/3.8.10

Release files / backtesting_package-1.2-py3-none-any.whl

Download URL backtesting_package-1.2-py3-none-any.whl
Size 11.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
00e2d67470ec1dc9be78da201a7be8b0255a430c47561fe0ad0cdba73c8db158
BLAKE2b-256 checksum
How to use checksums
cc2a740e9cf5484af17c27c4fd3c6027d3e5691c95937117bd5f6c9892934053
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.1.1 CPython/3.8.10

Release history Release notifications | RSS feed

This release

1.2 This release

2 release files

1.1

2 release files

1.0

2 release files

0.1

2 release files

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