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)
Built Distribution
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | 449d01385d1fae084fad3d98352d7fff8d940cd5a6c8c4bf246b1465ae5637e8 |
|
MD5 | dc01ffd1bb46afa89945d9a409284fa3 |
|
BLAKE2b-256 | 1838d01d42e6b7569d4c144ce6efbf6b860e7b69a8c84705cfad926ccef9966a |
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5d8880941a2e346a2d7aec91b0651538304a3f8e1e95ef70fcdcf254c956455c |
|
MD5 | cca1fea5a4e283bc90cca24bca2df809 |
|
BLAKE2b-256 | a6f398404bd91abb106507f6700824d90da888dcc7496bc60f33b9c9675b780c |