Metadata-Version: 2.4
Name: bcbpy
Version: 2.3.0
Summary: Python client for the Banco Central do Brasil SGS (Sistema Gerenciador de Series Temporais) API with 114 curated series codes for Brazilian economic and financial time series.
Author-email: Rodrigo Teodoro <rteoo@users.noreply.github.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/rteoo/bcbpy
Project-URL: Repository, https://github.com/rteoo/bcbpy
Project-URL: Issues, https://github.com/rteoo/bcbpy/issues
Project-URL: Changelog, https://github.com/rteoo/bcbpy/blob/main/CHANGELOG.md
Keywords: bcb,banco-central,brasil,brazil,sgs,economics,finance,time-series,ipca,selic,cdi,ptax
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Natural Language :: Portuguese (Brazilian)
Classifier: Natural Language :: English
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=1.5
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=4.0; extra == "dev"
Dynamic: license-file

# bcbpy

<p align="center">
  <img src="https://raw.githubusercontent.com/rteoo/bcbpy/main/docs/bcbpy-icon.svg" width="128" alt="bcbpy time-series icon">
</p>

<p align="center">
  A Python client for Banco Central do Brasil's SGS API, with pandas DataFrames,
  raw data provenance, and 114 curated economic series.
</p>

<p align="center">
  <a href="https://github.com/rteoo/bcbpy/actions/workflows/tests.yml"><img src="https://github.com/rteoo/bcbpy/actions/workflows/tests.yml/badge.svg" alt="Test status"></a>
  <a href="https://pypi.org/project/bcbpy/"><img src="https://img.shields.io/pypi/v/bcbpy?label=PyPI" alt="PyPI version"></a>
  <a href="https://pypi.org/project/bcbpy/"><img src="https://img.shields.io/pypi/pyversions/bcbpy" alt="Supported Python versions"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
</p>

Fetch Brazilian economic and financial time series from the
[Banco Central do Brasil](https://dadosabertos.bcb.gov.br/) SGS
(Sistema Gerenciador de Séries Temporais) API. Read exchange rates, interest
rates, inflation, GDP, employment, and more from Python without an API key.

## Highlights

- Date-indexed pandas DataFrames for individual series, recent observations,
  and multiple series merged into one table.
- **114 curated series codes across 14 categories**, plus keyword search and
  direct numeric SGS-code access.
- Raw payloads with request metadata, retrieval time, SHA-256, and parser
  version for reproducible data pipelines.
- Automatic partitioning of raw date ranges longer than SGS's 10-year
  single-query limit.
- Explicit HTTP and rate-limit errors, including `Retry-After` when provided.

## Installation

```bash
pip install bcbpy
```

Or from source:

```bash
git clone https://github.com/rteoo/bcbpy.git
cd bcbpy
pip install .
```

### Requirements

- Python 3.10+
- pandas
- requests

## Quick Start

```python
from bcbpy import fetch_series, fetch_last, fetch_multiple, INTEREST_RATES, EXCHANGE_RATES

# Last 10 CDI daily rates
cdi = fetch_last(INTEREST_RATES["CDI_DAILY"], n=10)
print(cdi)

# USD/BRL exchange rate for 2024
usd = fetch_series(EXCHANGE_RATES["USD_SALE_DAILY"], start_date="2024-01-01", end_date="2024-12-31")
print(usd)

# Multiple series merged into one DataFrame
df = fetch_multiple(
    {"CDI": INTEREST_RATES["CDI_DAILY"], "SELIC": INTEREST_RATES["SELIC_DAILY"]},
    start_date="2024-01-01",
    end_date="2024-12-31",
)
print(df.tail())
```

## API Reference

### Functions

#### `fetch_series(code, start_date=None, end_date=None)`

Fetch a time series by its SGS numeric code. Returns a pandas DataFrame indexed by date.

```python
from bcbpy import fetch_series

# Accepts YYYY-MM-DD or DD/MM/YYYY date formats
ipca = fetch_series(433, start_date="2023-01-01", end_date="2024-12-31")
```

Daily series (CDI, Selic, USD/BRL, …) require a `start_date`: BCB rejects undated daily queries with HTTP 406, which surfaces as `SGSHTTPError` carrying BCB's explanation.

#### `fetch_last(code, n=10)`

Fetch the last N observations of a series.

```python
from bcbpy import fetch_last

selic = fetch_last(11, n=5)
```

#### `fetch_multiple(codes_dict, start_date=None, end_date=None)`

Fetch multiple series and merge them into a single DataFrame, one column per series.

```python
from bcbpy import fetch_multiple

df = fetch_multiple({"CDI": 12, "SELIC": 11, "TR": 226}, start_date="2024-01-01")
```

#### `fetch_raw(code, start_date=None, end_date=None, transport=None)`

Fetch one SGS window as a `RawResult` (payload bytes plus request metadata). Same 10-year single-call limit as `fetch_series`. Does not parse the body.

```python
from bcbpy import fetch_raw

raw = fetch_raw(12, start_date="2024-01-01", end_date="2024-01-31")
print(raw.sha256, raw.source_url, raw.params)
```

#### `fetch_raw_range(code, start_date=None, end_date=None, transport=None)`

Like `fetch_raw`, but splits ranges longer than 10 years into bounded partitions. Adjacent partitions do not share a calendar day.

```python
from bcbpy import fetch_raw_range

parts = fetch_raw_range(433, start_date="2010-01-01", end_date="2024-12-31")
```

#### `list_codes(category=None)`

Print all available series codes. Pass a category name to filter.

```python
from bcbpy import list_codes

list_codes()                        # all 114 codes across 14 categories
list_codes("INTEREST_RATES")        # only interest rate codes
```

#### `search_codes(keyword)`

Search codes by keyword (case-insensitive). Returns a dict of matches.

```python
from bcbpy import search_codes

results = search_codes("IPCA")      # finds 15 IPCA-related codes
results = search_codes("USD")       # finds USD exchange rate codes
```

### Exceptions

| Exception | When |
|-----------|------|
| `SGSError` | Base class for every error raised by bcbpy, including malformed or non-JSON responses (e.g. an unknown series code) |
| `SGSHTTPError` | Any other HTTP error status, with BCB's error text in the message. Also a `requests.HTTPError`. |
| `SGSRateLimitError` | API returns HTTP 429 (too many requests). `retry_after` is set from `Retry-After` when present. |
| `SGSEmptyResponseError` | No data returned for the given query |

Network failures (timeouts, connection errors) are raised by `requests` unchanged.

### Error Handling

```python
from bcbpy import fetch_series, SGSRateLimitError, SGSEmptyResponseError

try:
    df = fetch_series(433, start_date="2024-01-01")
except SGSRateLimitError:
    print("Rate limited — wait and retry")
except SGSEmptyResponseError:
    print("No data for this date range")
```

## Available Series Codes

114 curated codes organized in 14 categories:

| Category | Series | Examples |
|----------|--------|----------|
| `EXCHANGE_RATES` | 6 | USD/BRL daily sale/purchase, monthly averages |
| `INTEREST_RATES` | 10 | Selic, CDI, TR, TBF, TJLP |
| `INFLATION` | 17 | IPCA, INPC, IGP-M, IGP-DI, IPC-Fipe |
| `IPCA_BREAKDOWN` | 11 | Tradeable, non-tradeable, durables, services, cores |
| `IPCA_CATEGORIES` | 9 | Food, housing, transport, health, education |
| `GDP` | 13 | GDP current/constant/USD, per capita, quarterly components |
| `EMPLOYMENT` | 7 | Unemployment rate, labor force, income |
| `INDUSTRIAL_PRODUCTION` | 6 | Manufacturing, mining, capital/intermediate/consumer goods |
| `FINANCIAL_MARKETS` | 7 | Gold, Bovespa, IMA-B |
| `SAVINGS` | 2 | Savings rate and return |
| `CONFIDENCE` | 4 | Consumer (ICC) and business (ICEI) confidence |
| `ECONOMIC_ACTIVITY` | 1 | IBC-Br (GDP proxy, seasonally adjusted) |
| `BASIC_BASKET` | 16 | Cost of living by capital city |
| `EXCHANGE_RATE_INDEX` | 5 | Effective currency basket and bilateral real indices (USD, JPY, DEM, ARS) |

Use any code directly by number or via the category dictionaries:

```python
from bcbpy import INFLATION, GDP

# These are equivalent:
fetch_series(433)
fetch_series(INFLATION["IPCA"])
```

### Migrating to 2.3

Version 2.3 renames seven registry keys to match the SGS series names.
**These are breaking changes to registry lookups.** Update dictionary lookups
and any saved key names using this table:

| Category | Old key (2.2 and earlier) | New key (2.3) | SGS code |
|----------|---------------|---------------|----------|
| `EMPLOYMENT` | `AVG_NOMINAL_INCOME` | `AVG_REAL_HABITUAL_INCOME` | 24382 |
| `INTEREST_RATES` | `SELIC_OVERNIGHT_ANNUAL` | `SELIC_MONTHLY_ANNUALIZED` | 4189 |
| `INTEREST_RATES` | `CDI_OVERNIGHT` | `CDI_MONTHLY_ANNUALIZED` | 4392 |
| `EXCHANGE_RATE_INDEX` | `REER_USD` | `RER_USD` | 11753 |
| `EXCHANGE_RATE_INDEX` | `REER_JPY` | `RER_JPY` | 11754 |
| `EXCHANGE_RATE_INDEX` | `REER_EUR` | `RER_DEM` | 11755 |
| `EXCHANGE_RATE_INDEX` | `REER_ARS` | `RER_ARS` | 11756 |

The old keys are removed from the category dictionaries and `ALL_CODES`;
lookups raise `KeyError`. `list_codes` and `search_codes` expose the new names.
Numeric SGS codes, the 114-series count, and fetch behavior are unchanged.

Code 24382 measures real habitual income of employed people. Codes 4189 and
4392 measure Selic and CDI accumulated over the month, annualized on a
252-day basis. `RER_DEM` is the Deutsche mark index. The four `RER_*` indices
are bilateral; `REER_BASKET` (11752) remains the effective currency-basket
index. All five exchange-rate indices are IPCA-based, with June 1994 = 100.

```python
from bcbpy import fetch_last, EMPLOYMENT, INTEREST_RATES, EXCHANGE_RATE_INDEX

income = fetch_last(EMPLOYMENT["AVG_REAL_HABITUAL_INCOME"])
selic = fetch_last(INTEREST_RATES["SELIC_MONTHLY_ANNUALIZED"])
dem = fetch_last(EXCHANGE_RATE_INDEX["RER_DEM"])
```

### Discontinued series

These registered series have stopped updating in SGS (last observation as of September 2026). Historical data is still available; recent windows return `SGSEmptyResponseError`.

| Series | Last observation |
|--------|------------------|
| `FINANCIAL_MARKETS`: `GOLD_BMF_GRAM`, `GOLD_LONDON_OZ`, `BOVESPA_INDEX`, `BOVESPA_VOLUME` | Sep 2019 |
| `EMPLOYMENT["FORMAL_EMPLOYMENT_TOTAL"]` | Dec 2019 |
| `INFLATION["ICV_DIEESE"]` | Feb 2020 |
| `FINANCIAL_MARKETS`: `IMA_B`, `IMA_B5`, `IMA_B5_PLUS` | May 2023 |
| `BASIC_BASKET` (all cities) | Jun 2025 |
| `INFLATION`: `IGP_M_1ST_DECENNIAL`, `IGP_M_2ND_DECENNIAL`, `IPC_FIPE_1ST_QUAD`, `IPC_FIPE_2ND_QUAD`, `IPC_FIPE_3RD_QUAD` | Jul 2025 |

## API Limits

- **Date range:** max 10 years per single query (BCB restriction since March 2025). `fetch_series` / `fetch_raw` still enforce that limit. `fetch_raw_range` splits longer windows into bounded requests.
- **Rate limiting:** HTTP 429 on excessive requests (no official limit documented). `SGSRateLimitError.retry_after` carries `Retry-After` when the API sends it; the client does not auto-retry.
- **Daily series:** a `start_date` is mandatory; undated queries return HTTP 406.
- **Unknown series codes:** SGS answers with an HTML page (HTTP 200) after about 30 seconds instead of a 404. With the client's 30-second timeout this usually surfaces as `requests.ReadTimeout`; when the page arrives in time it raises `SGSError`.
- **Date formats:** the client accepts both `YYYY-MM-DD` and `DD/MM/YYYY`

## Project Structure

```
bcbpy/
├── bcbpy/
│   ├── __init__.py      # Public API exports
│   ├── artifacts.py     # RawResult descriptor
│   ├── client.py        # API client functions and exceptions
│   ├── codes.py         # 114 curated series codes in 14 categories
│   └── constants.py     # Base URLs and API configuration
├── docs/
│   └── bcbpy-icon.svg   # Editable project icon
├── pyproject.toml       # PyPI packaging metadata
├── BCB_API_REFERENCE.md # SGS API reference and series code table
└── README.md
```

## Data Source

All data is fetched from the [BCB Open Data Portal](https://dadosabertos.bcb.gov.br/) under the [Open Database License (ODbL)](https://opendatacommons.org/licenses/odbl/).

## License

MIT (see [LICENSE](LICENSE)). The BCB data accessed through this client remains under ODbL; users must comply with ODbL when redistributing data.

## Releasing

The version in `bcbpy/__init__.py` must already be merged to `main`. From a clean checkout matching `origin/main`, run `python release.py --tag vX.Y.Z --dry-run`, then rerun without `--dry-run` and type the tag to confirm.

The release helper runs these gates locally: `python -m pytest -m "not integration" -v` and `python -m build`. It never bumps or commits `main`; PyPI publication remains the OIDC GitHub Actions workflow. A retry is safe only for the same tag when the existing tag points at the exact merged commit and no GitHub release exists. PyPI releases are immutable; rollback means following the package-recovery process rather than deleting or replacing a published version. The helper is platform-neutral Python, but hosted Actions behavior is not proven by local execution.
