Metadata-Version: 2.4
Name: fastbt-quant
Version: 0.6.0
Summary: Pandas-native backtesting library with backtest/live parity
Project-URL: Homepage, https://github.com/Vbhadala/fastbt
Project-URL: Issues, https://github.com/Vbhadala/fastbt/issues
Project-URL: Changelog, https://github.com/Vbhadala/fastbt/blob/main/CHANGELOG.md
Author: Vinod Bhadala
License: MIT
License-File: LICENSE
Keywords: backtesting,pandas,quant,strategy,trading
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.11
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: pydantic>=2.0
Provides-Extra: data
Requires-Dist: requests>=2.28; extra == 'data'
Provides-Extra: dev
Requires-Dist: aiohttp>=3.9; extra == 'dev'
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-aiohttp>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.3; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Provides-Extra: report
Requires-Dist: quantstats>=0.0.62; extra == 'report'
Provides-Extra: signal
Requires-Dist: aiohttp>=3.9; extra == 'signal'
Description-Content-Type: text/markdown

# fastbt-quant

Pandas-native backtesting library with backtest/live parity.

[![PyPI](https://img.shields.io/pypi/v/fastbt-quant.svg)](https://pypi.org/project/fastbt-quant/)
[![Python](https://img.shields.io/pypi/pyversions/fastbt-quant.svg)](https://pypi.org/project/fastbt-quant/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

> Distribution name on PyPI is **`fastbt-quant`**; the import name stays **`fastbt`**.

## Install

```bash
pip install fastbt-quant                  # core
pip install "fastbt-quant[data]"          # + idata.fyi data fetching
pip install "fastbt-quant[report]"        # + HTML tearsheets via quantstats
pip install "fastbt-quant[signal]"        # + live signal server (aiohttp)
pip install "fastbt-quant[data,report,signal]"   # everything
```

Requires Python 3.11+.

## Design principles

- **BYO data.** Pandas DataFrame in, `BacktestResult` out. No data providers bundled.
- **One strategy class, two run modes.** Same code for backtest and (eventually) live — the engine swaps, the strategy doesn't.
- **PaperBroker only in v0.0.1.** Real broker adapters (Alpaca, CCXT, Kite) arrive in v0.2+.
- **Declarative JSON strategies coming in v0.0.2.** The Python subclass API stays as the escape hatch.

## Quick start

```python
from fastbt import Strategy, backtest
from fastbt.indicators import sma

class SmaCross(Strategy):
    def on_bar(self, ctx):
        if len(ctx.history) < 30:
            return
        close = ctx.history["close"]
        fast = sma(close, 10).iloc[-1]
        slow = sma(close, 30).iloc[-1]
        prev_fast = sma(close, 10).iloc[-2]
        prev_slow = sma(close, 30).iloc[-2]

        if prev_fast <= prev_slow and fast > slow and ctx.position.is_flat:
            ctx.buy(qty=100)
        elif prev_fast >= prev_slow and fast < slow and ctx.position.is_long:
            ctx.close()

# `data` is any OHLCV DataFrame — bring your own source.
result = backtest(SmaCross(), data=data, initial_cash=100_000)
print(result)
# BacktestResult(trades=7, return=12.40%, max_dd=5.20%, sharpe=1.18, win_rate=57.1%)
```

## JSON strategies (v0.4.0)

Single-symbol and portfolio strategies can be expressed entirely in JSON. One
loader handles both:

```python
import json
from fastbt import load_strategy, portfolio_backtest

with open("examples/momentum_portfolio.json") as f:
    strategy = load_strategy(json.load(f))

# `data` is a dict[str, pd.DataFrame] for portfolio mode.
result = portfolio_backtest(strategy, data=universe, initial_cash=1_000_000)
```

A portfolio JSON spec describes the same lifecycle (`universe → rank → select →
allocate → schedule`) the Python `PortfolioStrategy` exposes. See
`examples/momentum_portfolio.json` for a complete momentum-rotation example.

## OHLCV schema contract

Your DataFrame must have:

- Columns: `open`, `high`, `low`, `close`, `volume` (case-insensitive)
- Index: `pd.DatetimeIndex`, monotonic increasing, no duplicates
- No NaN values
- `high >= max(open, close)`, `low <= min(open, close)`, `volume >= 0`

Call `fastbt.validate_ohlcv(df)` at your ingest boundary to fail fast.

## Fill policy (important)

The single biggest source of "backtest was profitable but live loses money" is fill timing. `fastbt` makes this explicit:

- `fill_policy="next_open"` (default, realistic) — order submitted on bar N fills at bar N+1's open. You only knew close *after* bar N ended.
- `fill_policy="current_close"` (optimistic) — fills at bar N's close. Use only when your signal is known intra-bar.

## Develop

```bash
git clone https://github.com/Vbhadala/fastbt
cd fastbt
uv sync --extra dev
uv run pytest
uv run python examples/sma_crossover.py
```

## Portfolio strategies (v0.3.0)

Built-in allocators and risk controls so portfolio strategies don't
hand-code these every time.

```python
from fastbt import PortfolioStrategy, portfolio_backtest, RebalanceSchedule
from fastbt.allocators import (
    annualized_volatility, inverse_volatility, momentum_score,
)
from fastbt.indicators import roc

class MomentumTop3(PortfolioStrategy):
    def rank(self, ctx, universe):
        scored = []
        for sym in universe:
            close = ctx.history[sym]["close"]
            score = momentum_score(
                {"r90d": roc(close, 90).iloc[-1] if len(close) >= 90 else None},
                {"r90d": 1.0},
            )
            scored.append((sym, score))
        return sorted(scored, key=lambda x: x[1], reverse=True)

    def select(self, ctx, ranked):
        return [s for s, _ in ranked[:3]]

    def allocate(self, ctx, selected):
        vols = {s: annualized_volatility(ctx.history[s]["close"], 60) for s in selected}
        return inverse_volatility(selected, vols)

result = portfolio_backtest(
    MomentumTop3(), data,
    schedule=RebalanceSchedule.monthly(day=1),
    min_drift_pct=2.0,                  # skip trades under 2% drift
    vol_trailing_multiplier=2.0,        # vol-adaptive trailing stop
    vol_trailing_fallback_pct=10.0,
    risk_free_rate=6.0,                 # for Sharpe / Sortino
)

print(result.metrics.cagr, result.metrics.sortino)
print(len(result.stop_outs), "stop-outs recorded")
```

See `examples/momentum_portfolio.py` for a complete runnable walkthrough.

## Roadmap

| Version | Scope |
|---------|-------|
| v0.0.1  | Single-symbol backtest, `PaperBroker`, 6 indicators, OHLCV validator |
| v0.0.2  | JSON declarative strategies, short positions, 15 more indicators |
| v0.1.0  | Portfolio/universe strategies (rank + select), live mode via async feed |
| v0.2.0  | Live signal engine + webhook server + idata.fyi data module |
| v0.3.0  | Allocators, vol-trailing stops, drift-aware rebalancing, CAGR/Sortino metrics |
| v0.4.0  | JSON portfolio strategy spec, vol_trailing in single-symbol JSON |
| v0.5.0  | First PyPI release as `fastbt-quant`. Symbol-aware fills (breaking: `Bar` now requires `symbol`). |
| v0.6.0+ | Real broker adapters (Alpaca/Kite), walk-forward validation, intrabar stop simulation |

## License

MIT
