# jbqlab

[![PyPI version](https://img.shields.io/pypi/v/jbqlab.svg)](https://pypi.org/project/jbqlab/)
[![CI](https://github.com/JaiAnshSB26/jbqlab/actions/workflows/ci.yml/badge.svg)](https://github.com/JaiAnshSB26/jbqlab/actions/workflows/ci.yml)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**A local-first quantitative backtesting and strategy analytics toolkit.**

jbqlab lets you backtest trading strategies on your own price data using a simple CLI or a clean Python SDK - all locally, no cloud account needed.

---

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Data Format](#data-format)
- [CLI Reference](#cli-reference)
  - [strategies](#strategies)
  - [info](#info)
  - [validate](#validate)
  - [backtest](#backtest)
  - [optimize](#optimize)
  - [report](#report)
  - [benchmark](#benchmark)
  - [montecarlo](#montecarlo)
  - [portfolio](#portfolio)
- [Python SDK](#python-sdk)
- [Available Strategies](#available-strategies)
- [Output Files](#output-files)
- [Metrics Reference](#metrics-reference)
- [Development](#development)

---

## Installation

```bash
pip install jbqlab
```

Python 3.10 or later is required. No sign-up, no API keys.

---

## Quick Start

**CLI - first backtest in 30 seconds:**

```bash
# Download or create a CSV with date,close columns, then:
jbqlab validate data.csv
jbqlab backtest data.csv --strategy buy_and_hold
jbqlab backtest data.csv --strategy sma_crossover --param fast=10 --param slow=30
```

**Python SDK - in three lines:**

```python
from jbqlab import run_backtest

result = run_backtest("data.csv", strategy="sma_crossover", fast=10, slow=30)
print(result.metrics)
# {'total_return': 0.28, 'cagr': 0.14, 'sharpe': 0.96, 'max_drawdown': 0.21, ...}
```

---

## Data Format

jbqlab expects a CSV file with at minimum a **date** column and a **close** column.

```
date,close
2020-01-02,3257.85
2020-01-03,3234.85
2020-01-06,3246.28
...
```

- **date** - any parseable date string (YYYY-MM-DD recommended)
- **close** - adjusted closing price (numeric)
- Additional columns (open, high, low, volume) are accepted but not required by most strategies

Validate your file first:

```bash
jbqlab validate data.csv --quality
```

---

## CLI Reference

Run `jbqlab --help` or `jbqlab <command> --help` for full option details.

### strategies

List all available strategies and their parameters.

```bash
jbqlab strategies
```

### info

Show detailed information about a specific strategy, including its parameters and default values.

```bash
jbqlab info <strategy_name>

# Examples
jbqlab info sma_crossover
jbqlab info rsi
```

### validate

Check a CSV file for backtesting compatibility.

```bash
jbqlab validate data.csv
jbqlab validate data.csv --ohlcv   # also check OHLCV consistency
jbqlab validate data.csv --quality # show data quality report
```

### backtest

Run a backtest. Strategy parameters are passed with `--param key=value` and can be repeated.

```bash
# Default strategy
jbqlab backtest data.csv

# Strategy with parameters
jbqlab backtest data.csv --strategy sma_crossover --param fast=10 --param slow=30
jbqlab backtest data.csv --strategy rsi --param period=14 --param oversold=30
jbqlab backtest data.csv --strategy bollinger_bands --param window=20 --param num_std=2.5
jbqlab backtest data.csv --strategy macd \
  --param fast_period=12 --param slow_period=26 --param signal_period=9
jbqlab backtest data.csv --strategy momentum --param lookback=20

# Transaction costs and capital
jbqlab backtest data.csv --strategy sma_crossover \
  --param fast=10 --param slow=30 \
  --cost 0.001 --slippage 0.0005 --capital 50000

# Save to a custom directory
jbqlab backtest data.csv --strategy buy_and_hold --out my_results/

# Suppress progress output
jbqlab backtest data.csv --strategy buy_and_hold --quiet
```

| Option | Default | Description |
|--------|---------|-------------|
| `--strategy`, `-s` | `buy_and_hold` | Strategy name |
| `--param`, `-p` | - | `key=value` parameter (repeatable) |
| `--out`, `-o` | `results/` | Output directory |
| `--cost` | `0.001` | Transaction cost fraction (0.1%) |
| `--slippage` | `0.0005` | Slippage fraction (0.05%) |
| `--capital` | `100000` | Initial capital |
| `--no-plot` | false | Skip equity curve plot |
| `--quiet`, `-q` | false | Suppress output |

### optimize

Grid-search for the best strategy parameters.

```bash
jbqlab optimize data.csv --strategy sma_crossover --metric sharpe
jbqlab optimize data.csv --strategy rsi --metric calmar
jbqlab optimize data.csv --strategy sma_crossover \
  --fast-min 5 --fast-max 30 --fast-step 5 \
  --slow-min 20 --slow-max 60 --slow-step 10 \
  --metric total_return
```

Built-in grids: `sma_crossover`, `mean_reversion`, `rsi`, `bollinger_bands`, `momentum`.
For custom grids use the [Python SDK](#python-sdk).

### report

Generate a full markdown report with equity curve, metrics table, and trade log.

```bash
jbqlab report data.csv --strategy sma_crossover --param fast=10 --param slow=30
jbqlab report data.csv --strategy buy_and_hold --title "My Portfolio Report" --out report/
```

### benchmark

Run all 12 strategies on the same dataset and produce a ranked comparison table.

```bash
jbqlab benchmark data.csv
jbqlab benchmark data.csv --out benchmark_results/ --no-plots
```

### montecarlo

Bootstrap Monte Carlo simulation on a strategy's return distribution.

```bash
jbqlab montecarlo data.csv --strategy buy_and_hold
jbqlab montecarlo data.csv --strategy sma_crossover \
  --param fast=10 --param slow=30 \
  --simulations 20000 --horizon 252 --seed 42
```

### portfolio

Detailed portfolio and risk statistics (Sharpe, Sortino, VaR, CVaR, win rate, etc.).

```bash
jbqlab portfolio data.csv --strategy buy_and_hold
jbqlab portfolio data.csv --strategy rsi --param period=14
```

---

## Python SDK

```python
from jbqlab import run_backtest, grid_search
from jbqlab.benchmark import run_benchmark, print_benchmark_summary
from jbqlab.montecarlo import monte_carlo_simulation
from jbqlab.portfolio import calculate_portfolio_stats
from jbqlab.plotting import plot_equity_curve
from jbqlab.reporting import save_results, generate_report
```

### run_backtest

```python
from jbqlab import run_backtest
import pandas as pd

# From a file path (str or Path)
result = run_backtest(
    "data.csv",
    strategy="sma_crossover",
    fast=10,
    slow=30,
    transaction_cost=0.001,   # 0.1% per trade
    slippage=0.0005,          # 0.05% per trade
    initial_capital=100_000,
)

# From a DataFrame
df = pd.read_csv("data.csv")
result = run_backtest(df, strategy="buy_and_hold")

# Access results
print(result.metrics)
# {'total_return': 0.285, 'cagr': 0.14, 'volatility': 0.31,
#  'sharpe': 0.557, 'sortino': 0.575, 'max_drawdown': 0.28, 'calmar': 0.50}

print(result.equity_curve.head())
# 2020-01-02    100000.00
# 2020-01-03    100014.23
# ...

print(result.positions.head())
# columns: date, close, signal, position, returns, equity
```

### grid_search

```python
from jbqlab import grid_search

result = grid_search(
    "data.csv",
    strategy="sma_crossover",
    param_grid={
        "fast":  [5, 10, 15, 20],
        "slow":  [20, 30, 40, 50],
    },
    optimize_metric="sharpe",  # or: total_return, calmar, sortino
    verbose=True,
)

print(result.best_params)
# {'fast': 10.0, 'slow': 20.0}

print(result.summary_df.head())
#    fast  slow  total_return   cagr  sharpe  max_drawdown
# 0  5.0  20.0         0.245  0.118   0.246         0.266
# ...
```

### Benchmark

```python
from jbqlab.benchmark import run_benchmark, print_benchmark_summary, save_benchmark

results = run_benchmark("data.csv")
print_benchmark_summary(results)     # Prints ranked table to stdout
save_benchmark(results, "output/")   # Saves CSV + equity curve plots
```

### Monte Carlo Simulation

```python
from jbqlab import run_backtest
from jbqlab.montecarlo import monte_carlo_simulation

result = run_backtest("data.csv", strategy="buy_and_hold")
returns = result.equity_curve.pct_change().dropna()

mc = monte_carlo_simulation(
    returns=returns,
    n_simulations=10_000,
    time_horizon=252,   # trading days
    random_seed=42,
)

print(mc.summary())
print(f"Probability of loss: {mc.prob_loss:.1%}")
print(f"5th percentile return: {mc.percentile_5:.2%}")
```

### Portfolio Statistics

```python
from jbqlab import run_backtest
from jbqlab.portfolio import calculate_portfolio_stats

result = run_backtest("data.csv", strategy="sma_crossover", fast=10, slow=30)
stats = calculate_portfolio_stats(result.equity_curve)

print(f"Sharpe:       {stats.sharpe_ratio:.2f}")
print(f"Max drawdown: {stats.max_drawdown:.2%}")
print(f"VaR (95%):    {stats.var_95:.2%}")
print(f"Win rate:     {stats.win_rate:.2%}")
```

### Save Results & Reports

```python
from jbqlab.reporting import save_results
from jbqlab.plotting import plot_equity_curve
from jbqlab.report import generate_report

save_results(result, "output/")        # metrics.json, equity_curve.csv, positions.csv
plot_equity_curve(result, "output/equity_curve.png")
generate_report(result, "output/report.md", title="My Backtest")
```

---

## Available Strategies

| Strategy | Key | Key Parameters |
|----------|-----|----------------|
| Buy and Hold | `buy_and_hold` | - |
| SMA Crossover | `sma_crossover` | `fast` (10), `slow` (30) |
| Mean Reversion | `mean_reversion` | `window` (20), `z_entry` (-2.0) |
| RSI | `rsi` | `period` (14), `oversold` (30), `overbought` (70) |
| MACD | `macd` | `fast_period` (12), `slow_period` (26), `signal_period` (9) |
| Bollinger Bands | `bollinger_bands` | `window` (20), `num_std` (2.0) |
| Momentum | `momentum` | `lookback` (20), `threshold` (0.0) |
| Dual Momentum | `dual_momentum` | `lookback` (12) |
| Donchian Channel | `donchian_channel` | `window` (20) |
| Volatility Breakout | `volatility_breakout` | `window` (20), `atr_mult` (1.5) |
| Trailing Stop | `trailing_stop` | `trail_pct` (0.05) |
| Triple SMA | `triple_sma` | `fast` (5), `mid` (20), `slow` (60) |

Run `jbqlab strategies` for live parameter docs, or `jbqlab info <strategy>` for a single strategy.

---

## Output Files

`jbqlab backtest` (and `report`) saves the following files to `--out` (default: `results/`):

| File | Description |
|------|-------------|
| `metrics.json` | All performance metrics as JSON |
| `equity_curve.csv` | Date-indexed equity curve |
| `positions.csv` | Full position/signal log |
| `equity_curve.png` | Equity curve chart |
| `report.md` | Markdown report (report command only) |

---

## Metrics Reference

| Metric | Description |
|--------|-------------|
| `total_return` | Total % return over the period |
| `cagr` | Compound Annual Growth Rate |
| `volatility` | Annualised standard deviation of returns |
| `sharpe` | Sharpe ratio (risk-free rate = 0) |
| `sortino` | Sortino ratio (downside deviation) |
| `max_drawdown` | Maximum peak-to-trough drawdown |
| `calmar` | CAGR / max drawdown |

Portfolio Stats (via `jbqlab portfolio` or `calculate_portfolio_stats()`):
`var_95`, `cvar_95`, `skewness`, `kurtosis`, `win_rate`, `profit_factor`,
`avg_win`, `avg_loss`, `best_day`, `worst_day`

---

## Development

```bash
git clone https://github.com/JaiAnshSB26/jbqlab.git
cd jbqlab
pip install -e ".[dev]"

# Run tests
pytest tests/ -q

# Lint
ruff check src/ tests/

# Build a distribution
python -m build
```

---

**Author**: Jai Ansh Bindra  
**License**: MIT  
**Changelog**: [CHANGELOG.md](CHANGELOG.md)
