Mini Backtest
==============
A lightweight, pandas‑first toolkit for quick backtesting experiments. It focuses on the essentials: generating reproducible sample OHLCV data, transforming data into analysis‑friendly shapes, and computing core performance metrics such as CAGR and Maximum Drawdown. The API is intentionally small, composable, and easy to integrate with your own research code.
Features
--------
- Simple, pandas‑centric API for inputs and outputs.
- Deterministic sample data generation for demos and tests.
- Core metrics: CAGR and Maximum Drawdown.
- Minimal `Backtester` wrapper that computes metrics from close‑price DataFrames.
Installation
------------
```
pip install mini_backtest
```
Quick Start
-----------
```python
from mini_backtest import load_sample_prices, Backtester
# Load a reproducible wide close‑price DataFrame (index=date, columns=symbol)
prices = load_sample_prices(n_symbols=10, start="2020-01-01", end="2022-12-31", seed=42)
# Compute per‑symbol metrics
res = Backtester(prices).run()
print(res.head())
```
API Overview
------------
- `mini_backtest.data`
- `load_sample_dataset(n_symbols=10, start="2020-01-01", end="2022-12-31", seed=42) -> pd.DataFrame`
- Returns a reproducible tidy OHLCV DataFrame without writing to disk.
- `load_sample_prices(n_symbols=10, start="2020-01-01", end="2022-12-31", seed=42) -> pd.DataFrame`
- Returns a reproducible wide close-price DataFrame.
- `generate_sample_data(file_path, symbols=None, start="2020-01-01", end="2022-12-31", seed=42) -> Path`
- Writes the simulated dataset to a JSON file for offline use.
- `load_data(file_path) -> pd.DataFrame`
- Loads the JSON dataset into a tidy DataFrame with columns: `date, symbol, open, high, low, close, volume`.
- `pivot_close(df) -> pd.DataFrame`
- Pivots the tidy data to a wide close‑price DataFrame (index = date, columns = symbols).
- `mini_backtest.metrics`
- `compute_cagr(prices, periods_per_year=252) -> pd.Series`
- `compute_max_drawdown(prices) -> pd.Series`
- `compute_metrics(prices, periods_per_year=252) -> pd.DataFrame`
- `mini_backtest.portfolio`
- `calc_returns(prices) -> pd.DataFrame`
- Converts a wide price DataFrame into single-period simple returns (date-sorted).
- `equity_curve_from_weights(prices, weights, start_value=1.0) -> pd.Series`
- Computes a portfolio equity curve using previous-day weights to avoid lookahead.
- `mini_backtest.backtester`
- `Backtester(prices: pd.DataFrame, periods_per_year: int = 252)`
- `Backtester.run() -> pd.DataFrame`
Design Notes
------------
- This project is intentionally minimal and metric‑oriented. It does not attempt to be an event‑driven engine or a full execution simulator.
- Inputs and outputs are pandas DataFrames to maximize composability with your own research pipeline.
Requirements
------------
- Python 3.8+
- `pandas>=1.4`, `numpy>=1.22`
Testing (optional)
------------------
If you clone the repository and want to run the tests:
```
pytest -q
```
Versioning
----------
This package follows semantic versioning for public APIs exposed in `mini_backtest`.
Security & Privacy
------------------
The published distribution includes only the `mini_backtest` package and this README. It does not ship any credentials, local configuration, or example datasets.
More Examples
-------------
The following examples demonstrate how to use `mini_backtest` to run simple backtests and both write results to a file and print them. These examples require no external data; a reproducible OHLCV sample is generated on demand with a fixed random seed.
Example 1: Equal-weight Buy & Hold
----------------------------------
```python
from pathlib import Path
import numpy as np
import pandas as pd
from mini_backtest import load_sample_prices, Backtester
from mini_backtest.portfolio import equity_curve_from_weights
# 1) Load reproducible sample prices (wide DataFrame)
prices = load_sample_prices(seed=42)
# 2) Build equal-weight portfolio weights
n = prices.shape[1]
weights = pd.DataFrame(
np.full_like(prices, fill_value=1.0 / n, dtype=float),
index=prices.index,
columns=prices.columns,
)
# 3) Compute equity using previous-day weights (no lookahead)
equity = equity_curve_from_weights(prices, weights, start_value=1.0)
# 4) Compute portfolio-level metrics (CAGR / Max Drawdown)
bt = Backtester(prices=equity.to_frame())
metrics = bt.run()
# 5) Write to file + print
out_path = Path('examples/output_buy_and_hold.json')
out_path.parent.mkdir(parents=True, exist_ok=True)
metrics.reset_index().to_json(out_path, orient='records')
print('Saved:', out_path)
print(metrics)
```
Example 2: Moving Average Crossover (SMA 20/50)
-----------------------------------------------
```python
from pathlib import Path
import numpy as np
from mini_backtest import load_sample_prices, Backtester
from mini_backtest.portfolio import equity_curve_from_weights
short, long = 20, 50
prices = load_sample_prices(seed=42)
sma_s = prices.rolling(short, min_periods=1).mean()
sma_l = prices.rolling(long, min_periods=1).mean()
long_mask = (sma_s > sma_l).astype(float)
denom = long_mask.sum(axis=1).replace(0, np.nan)
weights = long_mask.div(denom, axis=0).fillna(0.0) # Equal-weight among symbols with active signal
equity = equity_curve_from_weights(prices, weights, start_value=1.0)
metrics = Backtester(prices=equity.to_frame()).run()
out_path = Path('examples/output_ma_crossover.json')
out_path.parent.mkdir(parents=True, exist_ok=True)
metrics.reset_index().to_json(out_path, orient='records')
print('Saved:', out_path)
print(metrics)
```
Example 3: Momentum Timing (Top 30% equal-weight)
-------------------------------------------------
```python
from pathlib import Path
import numpy as np
from mini_backtest import load_sample_prices, Backtester
from mini_backtest.portfolio import equity_curve_from_weights
lookback, top_pct = 20, 0.7 # Rank by past 20-day return; select percentile >= 0.7
prices = load_sample_prices(seed=42)
momentum = prices.pct_change(lookback)
ranks = momentum.rank(axis=1, pct=True)
long_mask = (ranks >= top_pct).astype(float)
denom = long_mask.sum(axis=1).replace(0, np.nan)
weights = long_mask.div(denom, axis=0).fillna(0.0)
equity = equity_curve_from_weights(prices, weights, start_value=1.0)
metrics = Backtester(prices=equity.to_frame()).run()
out_path = Path('examples/output_momentum_top30.json')
out_path.parent.mkdir(parents=True, exist_ok=True)
metrics.reset_index().to_json(out_path, orient='records')
print('Saved:', out_path)
print(metrics)
```
Notes
-----
- Examples use `load_sample_prices()` to create in-memory, reproducible sample data; results are written to the `examples/` directory.
- You can also run the included scripts directly (see more strategies in `examples/`):
- `python examples/buy_and_hold.py`
- `python examples/ma_crossover.py`
- `python examples/momentum_top30.py`
Raw data
{
"_id": null,
"home_page": null,
"name": "mini-backtest",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "backtest, finance, numpy, pandas, trading",
"author": "Mini Backtest Maintainers",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/fc/02/1954ac58efe375132abb54f043137426232c6a2301ef1a3f9b2a8955ba99/mini_backtest-0.1.4.tar.gz",
"platform": null,
"description": "Mini Backtest\n==============\n\nA lightweight, pandas\u2011first toolkit for quick backtesting experiments. It focuses on the essentials: generating reproducible sample OHLCV data, transforming data into analysis\u2011friendly shapes, and computing core performance metrics such as CAGR and Maximum Drawdown. The API is intentionally small, composable, and easy to integrate with your own research code.\n\nFeatures\n--------\n\n- Simple, pandas\u2011centric API for inputs and outputs.\n- Deterministic sample data generation for demos and tests.\n- Core metrics: CAGR and Maximum Drawdown.\n- Minimal `Backtester` wrapper that computes metrics from close\u2011price DataFrames.\n\nInstallation\n------------\n\n```\npip install mini_backtest\n```\n\nQuick Start\n-----------\n\n```python\nfrom mini_backtest import load_sample_prices, Backtester\n\n# Load a reproducible wide close\u2011price DataFrame (index=date, columns=symbol)\nprices = load_sample_prices(n_symbols=10, start=\"2020-01-01\", end=\"2022-12-31\", seed=42)\n\n# Compute per\u2011symbol metrics\nres = Backtester(prices).run()\nprint(res.head())\n```\n\nAPI Overview\n------------\n\n- `mini_backtest.data`\n - `load_sample_dataset(n_symbols=10, start=\"2020-01-01\", end=\"2022-12-31\", seed=42) -> pd.DataFrame`\n - Returns a reproducible tidy OHLCV DataFrame without writing to disk.\n - `load_sample_prices(n_symbols=10, start=\"2020-01-01\", end=\"2022-12-31\", seed=42) -> pd.DataFrame`\n - Returns a reproducible wide close-price DataFrame.\n - `generate_sample_data(file_path, symbols=None, start=\"2020-01-01\", end=\"2022-12-31\", seed=42) -> Path`\n - Writes the simulated dataset to a JSON file for offline use.\n - `load_data(file_path) -> pd.DataFrame`\n - Loads the JSON dataset into a tidy DataFrame with columns: `date, symbol, open, high, low, close, volume`.\n - `pivot_close(df) -> pd.DataFrame`\n - Pivots the tidy data to a wide close\u2011price DataFrame (index = date, columns = symbols).\n\n- `mini_backtest.metrics`\n - `compute_cagr(prices, periods_per_year=252) -> pd.Series`\n - `compute_max_drawdown(prices) -> pd.Series`\n - `compute_metrics(prices, periods_per_year=252) -> pd.DataFrame`\n\n- `mini_backtest.portfolio`\n - `calc_returns(prices) -> pd.DataFrame`\n - Converts a wide price DataFrame into single-period simple returns (date-sorted).\n - `equity_curve_from_weights(prices, weights, start_value=1.0) -> pd.Series`\n - Computes a portfolio equity curve using previous-day weights to avoid lookahead.\n\n- `mini_backtest.backtester`\n - `Backtester(prices: pd.DataFrame, periods_per_year: int = 252)`\n - `Backtester.run() -> pd.DataFrame`\n\nDesign Notes\n------------\n\n- This project is intentionally minimal and metric\u2011oriented. It does not attempt to be an event\u2011driven engine or a full execution simulator.\n- Inputs and outputs are pandas DataFrames to maximize composability with your own research pipeline.\n\nRequirements\n------------\n\n- Python 3.8+\n- `pandas>=1.4`, `numpy>=1.22`\n\nTesting (optional)\n------------------\n\nIf you clone the repository and want to run the tests:\n\n```\npytest -q\n```\n\nVersioning\n----------\n\nThis package follows semantic versioning for public APIs exposed in `mini_backtest`.\n\nSecurity & Privacy\n------------------\n\nThe published distribution includes only the `mini_backtest` package and this README. It does not ship any credentials, local configuration, or example datasets.\n\nMore Examples\n-------------\n\nThe following examples demonstrate how to use `mini_backtest` to run simple backtests and both write results to a file and print them. These examples require no external data; a reproducible OHLCV sample is generated on demand with a fixed random seed.\n\nExample 1: Equal-weight Buy & Hold\n----------------------------------\n\n```python\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nfrom mini_backtest import load_sample_prices, Backtester\nfrom mini_backtest.portfolio import equity_curve_from_weights\n\n# 1) Load reproducible sample prices (wide DataFrame)\nprices = load_sample_prices(seed=42)\n\n# 2) Build equal-weight portfolio weights\nn = prices.shape[1]\nweights = pd.DataFrame(\n np.full_like(prices, fill_value=1.0 / n, dtype=float),\n index=prices.index,\n columns=prices.columns,\n)\n\n# 3) Compute equity using previous-day weights (no lookahead)\nequity = equity_curve_from_weights(prices, weights, start_value=1.0)\n\n# 4) Compute portfolio-level metrics (CAGR / Max Drawdown)\nbt = Backtester(prices=equity.to_frame())\nmetrics = bt.run()\n\n# 5) Write to file + print\nout_path = Path('examples/output_buy_and_hold.json')\nout_path.parent.mkdir(parents=True, exist_ok=True)\nmetrics.reset_index().to_json(out_path, orient='records')\nprint('Saved:', out_path)\nprint(metrics)\n```\n\nExample 2: Moving Average Crossover (SMA 20/50)\n-----------------------------------------------\n\n```python\nfrom pathlib import Path\nimport numpy as np\nfrom mini_backtest import load_sample_prices, Backtester\nfrom mini_backtest.portfolio import equity_curve_from_weights\n\nshort, long = 20, 50\nprices = load_sample_prices(seed=42)\n\nsma_s = prices.rolling(short, min_periods=1).mean()\nsma_l = prices.rolling(long, min_periods=1).mean()\nlong_mask = (sma_s > sma_l).astype(float)\ndenom = long_mask.sum(axis=1).replace(0, np.nan)\nweights = long_mask.div(denom, axis=0).fillna(0.0) # Equal-weight among symbols with active signal\n\nequity = equity_curve_from_weights(prices, weights, start_value=1.0)\nmetrics = Backtester(prices=equity.to_frame()).run()\n\nout_path = Path('examples/output_ma_crossover.json')\nout_path.parent.mkdir(parents=True, exist_ok=True)\nmetrics.reset_index().to_json(out_path, orient='records')\nprint('Saved:', out_path)\nprint(metrics)\n```\n\nExample 3: Momentum Timing (Top 30% equal-weight)\n-------------------------------------------------\n\n```python\nfrom pathlib import Path\nimport numpy as np\nfrom mini_backtest import load_sample_prices, Backtester\nfrom mini_backtest.portfolio import equity_curve_from_weights\n\nlookback, top_pct = 20, 0.7 # Rank by past 20-day return; select percentile >= 0.7\nprices = load_sample_prices(seed=42)\n\nmomentum = prices.pct_change(lookback)\nranks = momentum.rank(axis=1, pct=True)\nlong_mask = (ranks >= top_pct).astype(float)\ndenom = long_mask.sum(axis=1).replace(0, np.nan)\nweights = long_mask.div(denom, axis=0).fillna(0.0)\n\nequity = equity_curve_from_weights(prices, weights, start_value=1.0)\nmetrics = Backtester(prices=equity.to_frame()).run()\n\nout_path = Path('examples/output_momentum_top30.json')\nout_path.parent.mkdir(parents=True, exist_ok=True)\nmetrics.reset_index().to_json(out_path, orient='records')\nprint('Saved:', out_path)\nprint(metrics)\n```\n\nNotes\n-----\n\n- Examples use `load_sample_prices()` to create in-memory, reproducible sample data; results are written to the `examples/` directory.\n- You can also run the included scripts directly (see more strategies in `examples/`):\n - `python examples/buy_and_hold.py`\n - `python examples/ma_crossover.py`\n - `python examples/momentum_top30.py`\n",
"bugtrack_url": null,
"license": null,
"summary": "A minimal, extensible backtesting toolkit (CAGR, Max Drawdown).",
"version": "0.1.4",
"project_urls": null,
"split_keywords": [
"backtest",
" finance",
" numpy",
" pandas",
" trading"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "7b2efb123be9e3df1aaf72af32d3cfdb637004dea324fd6eead68ffc5a36995d",
"md5": "62d28a09ced41004c7cd66ae36d5f67d",
"sha256": "67474fca67d08f17c41b975a66cae48ad71dc4a865ef9b045c7360275466a8c3"
},
"downloads": -1,
"filename": "mini_backtest-0.1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "62d28a09ced41004c7cd66ae36d5f67d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 10573,
"upload_time": "2025-10-30T05:06:58",
"upload_time_iso_8601": "2025-10-30T05:06:58.330343Z",
"url": "https://files.pythonhosted.org/packages/7b/2e/fb123be9e3df1aaf72af32d3cfdb637004dea324fd6eead68ffc5a36995d/mini_backtest-0.1.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "fc021954ac58efe375132abb54f043137426232c6a2301ef1a3f9b2a8955ba99",
"md5": "b0f9661d646b5c5550bf54bcd27a56d9",
"sha256": "bd3b91069253365f7646a7181a9d2a230315e316cd00b75a5c604364dd9b53b7"
},
"downloads": -1,
"filename": "mini_backtest-0.1.4.tar.gz",
"has_sig": false,
"md5_digest": "b0f9661d646b5c5550bf54bcd27a56d9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 6354,
"upload_time": "2025-10-30T05:06:59",
"upload_time_iso_8601": "2025-10-30T05:06:59.708723Z",
"url": "https://files.pythonhosted.org/packages/fc/02/1954ac58efe375132abb54f043137426232c6a2301ef1a3f9b2a8955ba99/mini_backtest-0.1.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-30 05:06:59",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "mini-backtest"
}