Skip to main content

A backtesting framework for stock price prediction strategies

Project description

Stock Trading Backtest Framework

A Python framework for backtesting trading strategies with support for multiple order types and technical indicators. Licensed under the LGPL.


Install

pip install pyBacktest

Docs can be found in the wiki tab. Docs are a work in progress.


Features

Trading Operations

  • Market Orders (Buy/Sell)
  • Limit Orders
  • GTC (Good Till Canceled) Orders
  • Short Selling (Sell and Cover Short Positions)
  • Commission Handling
    • Flat Fee
    • Percentage
    • Per Share
  • Order Expiry Handling
    • Automatically cancels expired orders
  • Order Queue Management
    • Pending Orders handled by priority queue

Technical Indicators

  • Simple Moving Average (SMA)
  • Exponential Moving Average (EMA)
  • Relative Strength Index (RSI)
  • Bollinger Bands
  • MACD (Moving Average Convergence Divergence)
  • Crossover Detection (SMA, EMA, MACD)
  • Volume Weighted Average Price (VWAP)
  • Average True Range (ATR)
  • Indicator Calculation Integration
    • SMA, EMA, MACD available within strategy context

Portfolio Management

  • Position Tracking
    • Track open positions and entry points
  • Transaction History
    • Records details of executed trades
  • Portfolio Valuation
    • Calculates current portfolio value based on market prices
  • Cash Management
    • Tracks available cash for trades
  • Performance Metrics
    • Risk metrics, returns, and other performance statistics (e.g., Sharpe ratio, Drawdown, etc.)

Example

import pyBacktest as pbt
from pyBacktest.strategy import Strategy
from pyBacktest.utils import calculateSMA, analyzeResults, calculateMACD
from pyBacktest.tradeTypes import TradeType
from datetime import datetime
import pandas as pd

class SMACross(Strategy):
    def setup(self) -> None:
        self.has_position = False
        self.entry_price = 0
        self.sma20 = calculateSMA(self.data['Close'], 20)
        self.sma50 = calculateSMA(self.data['Close'], 50)

    def step(self, row: pd.Series) -> None:
        if row.name not in self.sma20.index or row.name not in self.sma50.index:
            return

        if self.backtest.cash>row['Close'] and self.sma20[row.name] > self.sma50[row.name]:
            self.has_position = True
            self.entry_price = row['Close']
            self.backtest.trade(TradeType.BUY, int(self.backtest.cash*0.95/row["Close"]), row['Close'], "DAY")
        elif self.has_position and self.sma20[row.name] < self.sma50[row.name]:
            self.has_position = False
            self.backtest.trade(TradeType.SELL, self.current_position, row['Close'], row.name)

class MACDStrategy(Strategy):
    def setup(self) -> None:
        self.macd, self.signal, self.histogram = calculateMACD(self.data['Close'])

    def step(self, row: pd.Series) -> None:
        if row.name not in self.macd.index:
            return

        if self.histogram[row.name] > 0:
            numShares = int(self.backtest.cash / row['Close'])
            trade_cost = self.backtest.calculate_trade_cost(TradeType.BUY, numShares, row['Close'])
            if self.backtest.cash >= trade_cost:
                self.backtest.trade(TradeType.BUY, numShares, row['Close'], "DAY")
        elif self.histogram[row.name] < 0 and self.backtest.getPosition() > 0:
            numShares = self.backtest.getPosition()
            trade_cost = self.backtest.calculate_trade_cost(TradeType.SELL, numShares, row['Close'])
            if self.backtest.cash >= trade_cost:
                self.backtest.trade(TradeType.SELL, numShares, row['Close'], row.name)

if __name__ == "__main__":
    sma_backtest = pbt.Backtest(
        strategy=SMACross(),
        ticker='AAPL',
        commision=1.00,
        startDate=datetime(2020, 1, 1),
        endDate=datetime(2024, 1, 1), 
        cash=10000
    )
    
    macd_backtest = pbt.Backtest(
        strategy=MACDStrategy(),
        ticker='AAPL',
        commision=1.00,
        startDate=datetime(2020, 1, 1),
        endDate=datetime(2024, 1, 1), 
        cash=10000
    )
    
    sma_results = sma_backtest.run()
    macd_results = macd_backtest.run()
    
    analyzeResults(sma_results)
    analyzeResults(macd_results)
    
    comparison = pbt.utils.compareBacktests(sma_results, macd_results)
    print("\nComparison of SMA and MACD Strategies:")
    print(f"Final Value Difference: ${comparison['final_value_diff']:,.2f}")
    print(f"Total Return Difference: {comparison['total_return_diff']:.2f}%")
    print(f"Number of Transactions Difference: {comparison['num_transactions_diff']}")
    print(f"Better Strategy: {comparison['better_strategy']}")

Diagrams

Diagrams are not guaranteed to be up to date.

Structure

classDiagram
    class Backtest {
        - strategy: Strategy
        - ticker: str
        - commission: float
        - startDate: datetime
        - endDate: datetime
        - cash: float
        + run() BacktestResult
        + trade(tradeType: TradeType, numShares: int, price: float, duration: str)
        + getPosition() int
        + calculate_trade_cost(tradeType: TradeType, numShares: int, price: float) float
    }

    class Strategy {
        # backtest: Backtest
        + setup() void
        + step(row: pd.Series) void
    }

    class SMACross {
        + setup() void
        + step(row: pd.Series) void
    }

    class MACDStrategy {
        + setup() void
        + step(row: pd.Series) void
    }

    Strategy <|-- SMACross
    Strategy <|-- MACDStrategy
    Backtest o--> Strategy
    Backtest o--> TradeType
    Backtest o--> Holding
    Backtest o--> Transaction
    Backtest o--> Order

    class TradeType {
        <<Enumeration>>
        + BUY
        + SELL
        + SHORT_SELL
        + STOP
        + COVER
        + GTC
        + MARKET_BUY
        + MARKET_SELL
        + SHORT_COVER
        + LIMIT_BUY
        + LIMIT_SELL
    }

    class Holding {
        - tradeType: TradeType
        - ticker: str
        - commission: float
        - executedSuccessfully: bool
        - numShares: int
        - totalCost: float
        - entryPrice: float
        - shortPosition: bool
    }

    class Transaction {
        - tradeType: TradeType
        - ticker: str
        - commission: float
        - executedSuccessfully: bool
        - numShares: int
        - pricePerShare: float
        - totalCost: float
        - date: datetime
        - profitLoss: float
        - notes: str
    }

    class Order {
        - tradeType: TradeType
        - ticker: str
        - numShares: int
        - targetPrice: float
        - duration: str
        - orderDate: datetime
        - active: bool
        - limitPrice: float
    }

    class Utils {
        + calculateSMA(data: pd.Series, period: int) pd.Series
        + calculateMACD(data: pd.Series, fastPeriod: int, slowPeriod: int, signalPeriod: int) tuple
        + analyzeResults(result: BacktestResult) dict
        + compareBacktests(result1: BacktestResult, result2: BacktestResult) dict
    }

    Backtest --> Utils

Backtest callchain

sequenceDiagram
    participant User as User
    participant Backtest as Backtest
    participant Strategy as Strategy
    participant Utils as Utils
    participant TradeTypes as TradeType
    participant DataLoader as DataLoader
    participant Results as BacktestResult

    User->>Backtest: Initialize Backtest(ticker, strategy, dates, cash)
    Backtest->>DataLoader: Load Historical Data
    DataLoader-->>Backtest: Return Historical Data
    Backtest->>Strategy: Instantiate Strategy
    Backtest->>Strategy: Call setup()
    Strategy-->>Backtest: Initialize Strategy Variables

    loop For each data row
        Backtest->>Strategy: Call step(row)
        Strategy->>TradeTypes: Create Trade (if conditions met)
        TradeTypes-->>Backtest: Return Trade Details
        Backtest->>Backtest: Update Portfolio & Cash
    end

    Backtest->>Results: Generate Backtest Results
    Results-->>Backtest: Return BacktestResult Object

    User->>Utils: Analyze Results(BacktestResult)
    Utils-->>User: Return Metrics and Insights
    User->>Backtest: Compare Strategies(optional)
    Backtest->>Utils: CompareBacktests(results1, results2)
    Utils-->>User: Return Comparison Summary

Components

graph TD
    A[Backtest Framework] --> B[Strategy Module]
    A --> C[Utils Module]
    A --> D[tradeTypes Module]
    A --> E[Data Handling]
    B --> F[Custom Strategies]
    C --> G[Indicator Calculations]
    C --> H[Risk Metrics]
    E --> I[Data Loader]
    E --> J[Data Validator]
    D --> K[TradeType Enum]
    D --> L[Order Class]

State Diagram

stateDiagram-v2
    state Backtest {
        [*] --> Initialized
        Initialized --> Running : run()
        Running --> Paused : pause()
        Running --> Completed : all data processed
        Paused --> Running : resume()
        Completed --> Archived : save results
    }

    state Order {
        [*] --> Pending
        Pending --> Executed : conditions met
        Pending --> Expired : time elapsed
        Executed --> Completed : trade settled
        Completed --> Archived : recorded in transactions
    }

Data Flow

graph TD
    A[Input Data] -->|Load| B[DataFrame]
    B -->|Validate| C[Backtest Engine]
    C -->|Feed| D[Strategy Execution]
    D -->|Generate| E[Trades]
    E -->|Record| F[Transactions]
    F -->|Summarize| G[Performance Analysis]
    G -->|Output| H[Results]

Interaction Overview

sequenceDiagram
    participant User
    participant Backtest
    participant Strategy
    participant Utils
    User->>Backtest: Initialize Backtest
    Backtest->>Utils: Load and validate data
    Backtest->>Strategy: Setup
    loop For each data point
        Backtest->>Strategy: step()
        Strategy->>Backtest: trade()
    end
    Backtest->>Utils: Analyze Results
    Backtest->>User: Return Results

Error Handling

flowchart TD
    A[Run Backtest] --> B[Trade Attempt]
    B -->|Funds Available| C[Execute Trade]
    B -->|Funds Insufficient| D[Raise InsufficientFundsError]
    C -->|Valid Trade| E[Update Transactions]
    C -->|Invalid Trade| F[Raise InvalidOrderError]
    E --> G[Continue Backtest]
    F --> G
    D --> G

Dependincies

graph TD
    A[SPDX Document: com.github.slowpoke111/pyBacktest] --> B[Numpy]
    A --> C[Pandas]
    A --> D[YFinance]
    A --> E[Typing Extensions]
    A --> F[GH Action: PyPI Publish]
    A --> G[GH Action: Checkout]
    A --> H[GH Action: Setup Python]

    B -->|Package Manager| P1[pypi/numpy]
    C -->|Package Manager| P2[pypi/pandas]
    D -->|Package Manager| P3[pypi/yfinance]
    E -->|Package Manager| P4[pypi/typing-extensions]
    F -->|Package Manager| P5[githubactions/pypa/gh-action-pypi-publish]
    G -->|Package Manager| P6[githubactions/actions/checkout]
    H -->|Package Manager| P7[githubactions/actions/setup-python]

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

pybacktest-1.1.5.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

pyBacktest-1.1.5-py3-none-any.whl (14.1 kB view details)

Uploaded Python 3

File details

Details for the file pybacktest-1.1.5.tar.gz.

File metadata

  • Download URL: pybacktest-1.1.5.tar.gz
  • Upload date:
  • Size: 14.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.20

File hashes

Hashes for pybacktest-1.1.5.tar.gz
Algorithm Hash digest
SHA256 449d01385d1fae084fad3d98352d7fff8d940cd5a6c8c4bf246b1465ae5637e8
MD5 dc01ffd1bb46afa89945d9a409284fa3
BLAKE2b-256 1838d01d42e6b7569d4c144ce6efbf6b860e7b69a8c84705cfad926ccef9966a

See more details on using hashes here.

File details

Details for the file pyBacktest-1.1.5-py3-none-any.whl.

File metadata

  • Download URL: pyBacktest-1.1.5-py3-none-any.whl
  • Upload date:
  • Size: 14.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.20

File hashes

Hashes for pyBacktest-1.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 5d8880941a2e346a2d7aec91b0651538304a3f8e1e95ef70fcdcf254c956455c
MD5 cca1fea5a4e283bc90cca24bca2df809
BLAKE2b-256 a6f398404bd91abb106507f6700824d90da888dcc7496bc60f33b9c9675b780c

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