Metadata-Version: 2.4
Name: bharatstock
Version: 0.1.0
Summary: Official Python client for the BharatStock API - reliable Indian stock market data (NSE/BSE).
Author: BharatStock
License: MIT
Project-URL: Homepage, https://bharatstockapi.com
Project-URL: Documentation, https://bharatstockapi.com/reference.html
Keywords: nse,bse,india,stocks,market-data,finance,api-client
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.24
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"

# BharatStock Python Client

Official Python client for the [BharatStock API](https://bharatstockapi.com) —
reliable Indian stock market data (NSE/BSE): EOD prices, quarterly/annual
financials, shareholding patterns, corporate actions, derived per-stock
metrics, a screener, bulk/block deals, insider trades, indices, and market-wide
FII/DII activity.

## Install

Once published:

```bash
pip install bharatstock
```

To use it locally from this repo before it's on PyPI (editable install):

```bash
pip install -e sdk/python
```

Requires Python 3.9+ and `httpx`.

## Authentication

Every data endpoint is authenticated with your `bsk_live_...` key, sent in the
`X-API-Key` header. Get one from the [dashboard](https://bharatstockapi.com).

```python
from bharatstock import BharatStock

# Pass the key explicitly...
client = BharatStock(api_key="bsk_live_...")

# ...or set BHARATSTOCK_API_KEY in the environment and omit it:
client = BharatStock()
```

## Quickstart

```python
from bharatstock import BharatStock

client = BharatStock(api_key="bsk_live_...")

# A single stock, with latest price + ~70 derived metrics
stock = client.stocks.get("RELIANCE")
print(stock.company_name, stock.exchange)
print("P/E:", stock.metrics.pe_ratio, "ROE:", stock.metrics.roe)

# Batch quotes for a watchlist (one call, up to 50 symbols)
for q in client.stocks.quotes(["TCS", "INFY", "HDFCBANK"]):
    print(q.symbol, q.close, q.change_pct)

# Search
for hit in client.search("tata"):
    print(hit.symbol, hit.company_name)

# Public data-integrity status (no key required)
print(client.status().status)   # "operational" | "degraded"
```

## Pagination

List endpoints return a `Page` object: iterate it directly for the rows, or read
`.total_pages` / `.has_next` to page through manually.

```python
# One page
page = client.stocks.prices("RELIANCE", from_date="2026-01-01", page_size=100)
print(page.total_items, page.total_pages)
for row in page:
    print(row.trade_date, row.close, row.adjusted_close)

# Auto-iterate every stock across all pages (lazy generator)
for s in client.stocks.iter_all(sector="Banking"):
    print(s.symbol)
```

Date ranges use `from_date=` / `to_date=` (sent to the API as `from` / `to`),
in `YYYY-MM-DD` form.

## Screener

```python
results = client.screener.run(
    filters=["pe_ratio.lt.15", "roe.gt.18", "market_cap.gt.10000"],  # Cr
    sort_by="roe",
    sort_order="desc",
    page_size=25,
)
for r in results:
    print(r.symbol, r.pe_ratio, r.roe)
```

Filter syntax is `metric.operator.value` where the operator is one of
`gt | lt | gte | lte | eq`. `market_cap` values are in Crores.

## Rate limits & retries

Plans have a daily request cap. When you exceed it the API returns **HTTP 429**.
The client automatically retries a 429 a few times with exponential backoff
(the API does not send a `Retry-After` header, so the wait is client-side); if
it's still capped it raises `RateLimitError`.

```python
from bharatstock import BharatStock, RateLimitError, NotFoundError

client = BharatStock(api_key="bsk_live_...", max_retries=3)

try:
    stock = client.stocks.get("NONEXISTENT")
except NotFoundError:
    print("no such ticker")
except RateLimitError as e:
    print("slow down:", e.detail)
```

All errors subclass `BharatStockError`, so you can catch that one type to handle
any API failure. Specific subclasses: `AuthenticationError` (401),
`NotFoundError` (404), `RateLimitError` (429), `BadRequestError` (400/422),
`APIError` (everything else).

## Resource map

| Group | Methods |
|-------|---------|
| `client.stocks` | `list`, `iter_all`, `get`, `quotes`, `compare`, `prices`, `financials`, `ratios`, `corporate_actions`, `technical_indicators`, `shareholding`, `mf_holdings`, `bulk_deals`, `block_deals`, `insider_trades` |
| `client.deals` | `bulk`, `block`, `insider_trades` (market-wide) |
| `client.screener` | `run` |
| `client.indices` | `list`, `prices` |
| `client.market` | `fii_dii` |
| top-level | `search`, `movers`, `price_shockers`, `status` |

## Notes

- **`market_cap` units differ by endpoint** (this mirrors the current API, so
  the client reports exactly what the server sends):
  - **Rupees**: `stocks.get`, `stocks.list` / `iter_all`, `search`
    (`StockSummary`/`StockDetail.market_cap`), `stocks.compare`
    (`ComparisonItem`), and `stocks.ratios` (`RatioSnapshot`).
  - **Crores** (1 Cr = 10,000,000): the metrics block on `stocks.get`
    (`StockDetail.metrics.market_cap`) and `screener.run` (`ScreenerResult`).
  - The `screener.run` `market_cap` **filter** value is also in **Crores**
    (e.g. `"market_cap.gt.10000"` = > 10,000 Cr).
  So `stock.market_cap` and `stock.metrics.market_cap` on the same object are
  in different units (rupees vs Crores) — divide the rupee value by 1e7 to
  compare. Convert with `crores = rupees / 10_000_000`.
- Use the client as a context manager (`with BharatStock(...) as c:`) to close
  the underlying HTTP connection pool when you're done.
- Types ship with the package (`py.typed`), so editors autocomplete every
  method and response field.

## License

MIT
