{"id":19129626,"url":"https://github.com/joshcap20/pybacktest","last_synced_at":"2025-10-10T04:44:28.701Z","repository":{"id":259671290,"uuid":"878840977","full_name":"JoshCap20/pybacktest","owner":"JoshCap20","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-28T08:50:40.000Z","size":67,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-11T05:44:52.094Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JoshCap20.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-26T08:54:18.000Z","updated_at":"2024-10-28T08:50:44.000Z","dependencies_parsed_at":"2025-01-03T10:44:15.814Z","dependency_job_id":"2c43996c-75a7-4a7e-ad57-9fd37f4165c4","html_url":"https://github.com/JoshCap20/pybacktest","commit_stats":null,"previous_names":["joshcap20/pybacktest"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JoshCap20/pybacktest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshCap20%2Fpybacktest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshCap20%2Fpybacktest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshCap20%2Fpybacktest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshCap20%2Fpybacktest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoshCap20","download_url":"https://codeload.github.com/JoshCap20/pybacktest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoshCap20%2Fpybacktest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002668,"owners_count":26083442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T06:07:59.272Z","updated_at":"2025-10-10T04:44:28.668Z","avatar_url":"https://github.com/JoshCap20.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PyBacktest\n\nPyBacktest 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.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Key Concepts](#key-concepts)\n  - [Indicators](#indicators)\n  - [Predicates](#predicates)\n  - [Strategies](#strategies)\n  - [Actions](#actions)\n- [API Reference](#api-reference)\n  - [DataFeed](#datafeed)\n  - [Indicators](#indicators-api)\n  - [Predicates](#predicates-api)\n  - [Strategies](#strategies-api)\n  - [Actions](#actions-api)\n  - [Backtest](#backtest)\n  - [Portfolio](#portfolio)\n- [Examples](#examples)\n  - [Indicator Demo: Feature Engineering](#indicator-demo-feature-engineering)\n  - [Strategy Demo: SMA EMA Crossover](#strategy-demo-sma-ema-crossover)\n- [Advanced Usage](#advanced-usage)\n  - [Creating Custom Indicators](#creating-custom-indicators)\n  - [Extending Strategies](#extending-strategies)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Overview\n\nCurrent backtesting frameworks are often overly complex and difficult to use. PyBacktest simplifies the process by providing:\n\n- A modular structure with clear components.\n- An intuitive API for defining indicators, predicates, strategies, and actions.\n- Easy integration with data sources like Yahoo Finance.\n- Flexibility to extend and customize components according to your needs.\n\nEvery 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.\n\n## Features\n\n- **Simple API**: Minimal boilerplate code to set up and run backtests.\n- **Modular Design**: Components like indicators and strategies are modular and reusable.\n- **Extensibility**: Easily create custom indicators and strategies.\n- **Data Integration**: Built-in support for fetching data from Yahoo Finance using `yfinance`.\n- **Real-Time Simulation**: Step through data points to simulate real-time trading decisions.\n\n## Installation\n\nInstall PyBacktest using pip:\n\n```bash\npip install pybacktest\n```\n\n## Quick Start\n\nHere's how you can get started with PyBacktest in just a few steps.\n\n```python\nfrom pybacktest import Backtest, YahooFinanceDataFeed\nfrom pybacktest.indicators import SMAIndicator, EMAIndicator\nfrom pybacktest.predicates import Predicate\nfrom pybacktest.strategies import Strategy\nfrom pybacktest.actions import BuyAction, SellAction\nimport operator\n\n# Define symbols and fetch data\nsymbols = [\"AAPL\", \"MSFT\", \"GOOGL\"]\ndata_feed = YahooFinanceDataFeed(symbols, start=\"2021-01-01\", end=\"2021-12-31\")\n\n# Add indicators\nsma_indicator = SMAIndicator(window=14)\nema_indicator = EMAIndicator(window=25)\ndata_feed.add_indicators([sma_indicator, ema_indicator])\n\n# Define predicates\nsma_below_ema = Predicate(sma_indicator, operator.lt, ema_indicator)\nsma_above_ema = Predicate(sma_indicator, operator.gt, ema_indicator)\n\n# Define actions\nbuy_action = BuyAction()\nsell_action = SellAction()\n\n# Define strategy\nstrategy = Strategy(\n        entry_conditions=[sma_below_ema],\n        entry_action=buy_action,\n        exit_conditions=[sma_above_ema],\n        exit_action=sell_action,\n)\n\n# Subscribe strategy to data feed\ndata_feed.subscribe(strategy)\n\n# Create and run backtest\nbacktest = Backtest(data_feed, initial_balance=10000)\nbacktest.run()\n```\n\n## Key Concepts\n\n### Indicators\n\nIndicators 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.\n\nExamples of indicators:\n\n- SMA (Simple Moving Average)\n- EMA (Exponential Moving Average)\n- RSI (Relative Strength Index)\n- MACD (Moving Average Convergence Divergence)\n\n### Predicates\n\nPredicates use indicators, comparison operators, and values to return a boolean. They form the logical conditions for strategies.\n\nExample Predicate:\n\n```python\nfrom pybacktest.strategies import Predicate\nimport operator\n\nsma_below_ema = Predicate(\n        input1=sma_indicator,\n        operator=operator.lt,\n        input2=ema_indicator\n)\n```\n\n### Strategies\n\nStrategies 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.\n\nExample Strategy:\n\n```python\nfrom pybacktest.strategies import Strategy\n\nstrategy = Strategy(\n        entry_conditions=[sma_below_ema],\n        entry_action=buy_action,\n        exit_conditions=[sma_above_ema],\n        exit_action=sell_action,\n)\n```\n\n### Actions\n\nActions are what happens when predicates evaluate to True in a strategy. Common actions include buying or selling assets.\n\nAvailable Actions:\n\n- BuyAction\n- SellAction\n\n## API Reference\n\n### DataFeed\n\nThe `DataFeed` class handles the data input for the backtest. It manages the data retrieval, indicator application, and strategy subscriptions.\n\nYahooFinanceDataFeed Example:\n\n```python\nfrom pybacktest.data.yahoo_finance_data_feed import YahooFinanceDataFeed\n\ndata_feed = YahooFinanceDataFeed(symbols=[\"AAPL\"], start=\"2021-01-01\", end=\"2021-12-31\")\n```\n\nMethods:\n\n- `add_indicators(indicators)`: Adds indicators to the data feed.\n- `subscribe(strategy)`: Subscribes a strategy to the data feed.\n\n### Indicators API\n\nIndicators calculate specific metrics based on historical data and add them to the DataFrame.\n\nBase Indicator Class:\n\n```python\nfrom pybacktest.indicators import Indicator\n\nclass Indicator:\n        def apply(self, data: pd.DataFrame) -\u003e None:\n                pass\n```\n\nBuilt-in Indicators:\n\n- `SMAIndicator(window)`\n- `EMAIndicator(window)`\n- `RSIIndicator(window)`\n- `MACDIndicator()`\n- `BollingerBandsIndicator(window)`\n- `ATRIndicator(window)`\n- `ADXIndicator(window)`\n- `OBVIndicator()`\n\nAdding Indicators:\n\n```python\nfrom pybacktest.indicators import SMAIndicator, EMAIndicator\n\nsma_indicator = SMAIndicator(window=14)\nema_indicator = EMAIndicator(window=25)\ndata_feed.add_indicators([sma_indicator, ema_indicator])\n```\n\n### Predicates API\n\nPredicates 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.\n\nPredicate Class:\n\n```python\nfrom pybacktest.strategies import Predicate\n\npredicate = Predicate(\n        input1=indicator_or_value,\n        operator=comparison_operator,\n        input2=indicator_or_value\n)\n```\n\nOperators:\n\n- `operator.lt` (less than)\n- `operator.gt` (greater than)\n- `operator.eq` (equal to)\n- `operator.ne` (not equal)\n- `operator.le` (less than or equal to)\n- `operator.ge` (greater than or equal to)\n\nExample:\n\n```python\nimport operator\n\nrsi_below_70 = Predicate(\n        input1=rsi_indicator,\n        operator=operator.lt,\n        input2=70  # Comparing RSI value to 70\n)\n\nvwamp_above_price = Predicate(\n    input1=IndicatorInput(vwamp_indicator, column_name=vwamp_indicator.indicator_name),\n    operator=operator.gt,\n    input2=\"Adj Close\",\n)\n\nsma_above_ema = Predicate(\n    input1=sma_indicator,\n    operator=operator.gt,\n    input2=ema_indicator,\n)\n```\n\n#### Predicate Extension via Bitwise Operators\n\nTo make using predicates even easier, you can use the following bitwise operators to extend the functionality of the `Predicate` class:\n\n- `\u0026` (and)\n- `|` (or)\n- `~` (not)\n\n```python\n# Example of combining predicates\ncombined_predicate = rsi_below_70 \u0026 sma_above_ema\n\n# Example of negating a predicate\nrsi_above_70 = ~rsi_below_70\n```\n\n**These are brand new and in testing stages. Please report any issues you may encounter.**\n\n### Strategies API\n\nStrategies combine predicates and actions to define trading logic.\n\nStrategy Class:\n\n```python\nfrom pybacktest.strategies import Strategy\n\nstrategy = Strategy(\n        entry_conditions=[predicate1, predicate2],\n        entry_action=buy_action,\n        exit_conditions=[predicate3],\n        exit_action=sell_action,\n)\n```\n\nMethods:\n\n- `apply(data, context)`: Applies the strategy to the data at each time step.\n\n### Actions API\n\nActions define what happens when a strategy's conditions are met.\n\nAction Base Class:\n\n```python\nfrom pybacktest.actions import Action\n\nclass Action:\n        def execute(self, data: pd.Series, context: dict) -\u003e None:\n                pass\n```\n\nAvailable Actions:\n\n- `BuyAction()`\n- `SellAction()`\n\nUsing Actions:\n\n```python\nfrom pybacktest.actions import BuyAction, SellAction\n\nbuy_action = BuyAction()\nsell_action = SellAction()\n```\n\n### Backtest\n\nThe `Backtest` class orchestrates the execution of the backtest.\n\nCreating a Backtest:\n\n```python\nfrom pybacktest import Backtest\n\nbacktest = Backtest(data_feed, initial_balance=10000)\n```\n\nRunning the Backtest:\n\n```python\nbacktest.run()\n```\n\n### Portfolio\n\nThe `Portfolio` class manages cash, positions, and transaction history.\n\nPortfolio Attributes:\n\n- `cash`: Current cash balance.\n- `positions`: Dictionary of symbol to quantity held.\n- `transaction_history`: List of transactions executed.\n\nPortfolio Methods:\n\n- `buy(symbol, quantity, price, timestamp)`: Buys a specified quantity of a symbol.\n- `sell(symbol, quantity, price, timestamp)`: Sells a specified quantity of a symbol.\n- `total_portfolio_value(current_prices)`: Calculates the total value of the portfolio.\n\n## Examples\n\n### Indicator Demo: Feature Engineering\n\nThis demo retrieves data for specified stocks and adds SMA and EMA indicators to the DataFrame.\n\n```python\nfrom pybacktest import YahooFinanceDataFeed\nfrom pybacktest.indicators import SMAIndicator, EMAIndicator\n\nsymbols = [\"AAPL\", \"MSFT\", \"GOOGL\"]\n\n# Initialize data feed\ndata_feed = YahooFinanceDataFeed(symbols, start=\"2021-01-01\", end=\"2021-12-31\")\n\n# Define indicators\nsma_indicator = SMAIndicator(window=50)\nema_indicator = EMAIndicator(window=20)\n\n# Add indicators to data feed\ndata_feed.add_indicators([sma_indicator, ema_indicator])\n\n# Display the modified DataFrame\nprint(data_feed._data.tail())\n```\n\n### Strategy Demo: SMA EMA Crossover\n\nThis strategy buys when the SMA crosses below the EMA and sells when the SMA crosses above the EMA.\n\n```python\nfrom pybacktest import Backtest, YahooFinanceDataFeed\nfrom pybacktest.indicators import SMAIndicator, EMAIndicator\nfrom pybacktest.strategies import Predicate, Strategy\nfrom pybacktest.actions import BuyAction, SellAction\nimport operator\n\n# Fetch data\nsymbols = [\"AAPL\", \"MSFT\", \"GOOGL\"]\ndata_feed = YahooFinanceDataFeed(symbols, start=\"2020-01-01\", end=\"2020-12-01\")\n\n# Add indicators\nsma_indicator = SMAIndicator(window=14)\nema_indicator = EMAIndicator(window=25)\ndata_feed.add_indicators([sma_indicator, ema_indicator])\n\n# Define predicates\nsma_below_ema = Predicate(\n        input1=sma_indicator,\n        operator=operator.lt,\n        input2=ema_indicator\n)\n\nsma_above_ema = Predicate(\n        input1=sma_indicator,\n        operator=operator.gt,\n        input2=ema_indicator\n)\n\n# Define actions\nbuy_action = BuyAction()\nsell_action = SellAction()\n\n# Define strategy\nstrategy = Strategy(\n        entry_conditions=[sma_below_ema],\n        entry_action=buy_action,\n        exit_conditions=[sma_above_ema],\n        exit_action=sell_action,\n)\n\n# Subscribe strategy to data feed\ndata_feed.subscribe(strategy)\n\n# Create and run backtest\nbacktest = Backtest(data_feed, initial_balance=10000)\nbacktest.run()\n```\n\n### Strategy Demo: VWAMP and Price Crossover\n\nThis strategy buys when the SMA crosses below the EMA and sells when the SMA crosses above the EMA.\n\n```python\nfrom pybacktest import Backtest, YahooFinanceDataFeed\nfrom pybacktest.indicators import VWAPIndicator\nfrom pybacktest.strategies import Predicate, Strategy, IndicatorInput\nfrom pybacktest.actions import BuyAction, SellAction\nfrom pybacktest.data.stock_groups import SPY_500\nimport operator\n\n# Fetch data\nsymbols = SPY_500\ndata_feed = YahooFinanceDataFeed(symbols, start=\"2020-01-01\", end=\"2020-12-01\")\n\n# Add indicators\nvwamp_indicator = VWAPIndicator(column=\"Adj Close\")\ndata_feed.add_indicators([vwamp_indicator])\n\n# Define predicates\n# Note: When an indicator has multiple column names, one must be explicitly specified via an IndicatorInput object\nvwamp_above_price = Predicate(\n    input1=IndicatorInput(vwamp_indicator, column_name=vwamp_indicator.indicator_name),\n    operator=operator.gt,\n    input2=\"Adj Close\",\n)\n\n# We can use the overridden operators to negate predicates\nvwamp_below_price = ~vwamp_above_price\n\n# Define actions\nbuy_action = BuyAction()\nsell_action = SellAction()\n\n# Define strategy\nstrategy = Strategy(\n    entry_conditions=[vwamp_above_price],\n    entry_action=buy_action,\n    exit_conditions=[vwamp_below_price],\n    exit_action=sell_action,\n)\n\n# Subscribe strategy to data feed\ndata_feed.subscribe(strategy)\n\n# Create and run backtest\nbacktest = Backtest(data_feed, initial_balance=10000)\nbacktest.run()\n```\n\n## Advanced Usage\n\n### Creating Custom Indicators\n\nYou can extend the `Indicator` base class to create custom indicators.\n\n```python\nfrom pybacktest.indicators import Indicator\n\nclass CustomIndicator(Indicator):\n        def __init__(self, window, column: str = \"Close\"):\n                self.window = windows\n                self.column = column\n                self.column_names = [f\"Custom_{window}\"]\n\n        def apply(self, data: pd.DataFrame) -\u003e None:\n                for symbol in data.columns.get_level_values(0).unique():\n                        data[(symbol, self.column_names[0])] = (\n                                data[(symbol, self.column)].rolling(window=self.window).mean()\n                        )\n```\n\nUsage:\n\n```python\ncustom_indicator = CustomIndicator(window=10)\ndata_feed.add_indicators([custom_indicator])\n```\n\n### Extending Strategies\n\nYou can create more complex strategies by combining multiple predicates and actions.\n\nExample:\n\n```python\nfrom pybacktest.indicators import RSIIndicator\nfrom pybacktest.predicates import Predicate\nimport operator\n\n# Add RSI indicator\nrsi_indicator = RSIIndicator(window=14)\ndata_feed.add_indicators([rsi_indicator])\n\n# Define additional predicates\nrsi_below_30 = Predicate(\n        input1=rsi_indicator,\n        operator=operator.lt,\n        input2=30\n)\n\n# Update strategy\nstrategy = Strategy(\n        entry_conditions=[sma_below_ema, rsi_below_30],\n        entry_action=buy_action,\n        exit_conditions=[sma_above_ema],\n        exit_action=sell_action,\n)\n```\n\n## Contributing\n\nContributions are welcome! If you'd like to contribute to PyBacktest, please follow these steps:\n\n1. Fork the repository.\n2. Create a new branch for your feature or bugfix.\n3. Submit a pull request with a detailed description of your changes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshcap20%2Fpybacktest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoshcap20%2Fpybacktest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoshcap20%2Fpybacktest/lists"}