Metadata-Version: 2.4
Name: bharatstock
Version: 0.1.2
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

```bash
pip install bharatstock
```

Requires Python 3.9+ (the only dependency is `httpx`).

To work on the client from a checkout of this repo, install it editable:

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

## 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).

## Method reference

Every method and its parameters. Types ship with the package (`py.typed`), so
your editor autocompletes each method and every field on the returned objects.
Keyword-only params show their default; `page`/`page_size` are omitted from the
notes below but accepted by every paginated method. Methods that return `Page`
are paginated (iterate directly, or use `.total_pages` / `.has_next`); the rest
return a single object or a plain list.

### `client.stocks`

| Method | Key parameters | Returns |
|--------|----------------|---------|
| `list(...)` | `q=None`, `sector=None`, `active_only=True` | `Page[StockSummary]` |
| `iter_all(...)` | `q=None`, `sector=None`, `active_only=True` (lazy, walks all pages) | iterator of `StockSummary` |
| `get(ticker, exchange=None)` | `ticker` accepts a symbol or ISIN; `exchange` = `"NSE"`/`"BSE"` to disambiguate a shared ticker | `StockDetail` |
| `quotes(symbols)` | `symbols`: list of up to 50 tickers | `list[QuoteItem]` (unknown symbols returned with `found=False`) |
| `compare(sector, ...)` | `sort` = `market_cap`\|`pe_ratio`\|`pb_ratio`\|`roe`\|`roce` (default `market_cap`), `limit=20` | `list[ComparisonItem]` |
| `prices(ticker, ...)` | `from_date=None`, `to_date=None` (`YYYY-MM-DD`), `exchange=None` | `Page[DailyPricePoint]` |
| `financials(ticker, ...)` | `period_type` = `quarterly`\|`annual` (default `quarterly`), `exchange=None` | `Page[FinancialPeriod]` |
| `ratios(ticker, exchange=None)` | — | `RatioSnapshot` |
| `corporate_actions(ticker, ...)` | `action_type=None` (`dividend`\|`bonus`\|`split`\|`rights`\|`buyback`), `exchange=None` | `Page[CorporateActionItem]` |
| `technical_indicators(ticker, ...)` | `from_date`, `to_date`, `sma_period=20`, `ema_period=20`, `rsi_period=14`, `exchange=None` | `Page[TechnicalIndicatorPoint]` |
| `shareholding(ticker, ...)` | `exchange=None` | `Page[ShareholdingPatternItem]` |
| `mf_holdings(ticker, ...)` | `month=None` (`YYYY-MM`), `exchange=None` | `Page[MFHoldingItem]` |
| `bulk_deals(ticker, ...)` | `buy_sell=None` (`BUY`\|`SELL`), `exchange=None` | `Page[DealItem]` |
| `block_deals(ticker, ...)` | `buy_sell=None` (`BUY`\|`SELL`), `exchange=None` | `Page[DealItem]` |
| `insider_trades(ticker, ...)` | `transaction_type=None` (`acquisition`\|`disposal`), `promoters_only=False`, `exchange=None` | `Page[InsiderTradeItem]` |

### `client.deals` (market-wide, across all stocks)

| Method | Key parameters | Returns |
|--------|----------------|---------|
| `bulk(...)` | `buy_sell=None` (`BUY`\|`SELL`) | `Page[DealItem]` |
| `block(...)` | `buy_sell=None` (`BUY`\|`SELL`) | `Page[DealItem]` |
| `insider_trades(...)` | `transaction_type=None` (`acquisition`\|`disposal`), `promoters_only=False` | `Page[InsiderTradeItem]` |

### `client.screener`

| Method | Key parameters | Returns |
|--------|----------------|---------|
| `run(...)` | `filters=None` (list of `metric.operator.value`), `sector=None`, `exchange=None`, `sort_by="market_cap"`, `sort_order="desc"` (`asc`\|`desc`) | `Page[ScreenerResult]` |

### `client.indices`

| Method | Key parameters | Returns |
|--------|----------------|---------|
| `list(...)` | `category=None`, `active_only=True` | `Page[IndexSummary]` |
| `prices(name, ...)` | `from_date=None`, `to_date=None` (`YYYY-MM-DD`) | `Page[IndexPricePoint]` |

### `client.market`

| Method | Key parameters | Returns |
|--------|----------------|---------|
| `fii_dii(...)` | `from_date=None` (default 30 days before `to`), `to_date=None` (default today), `limit=30`, `latest=False` (only the single most recent day; ignores from/to/limit) | `FiiDiiActivity` (`.data` is a list of `FiiDiiDay`) |

### Top-level helpers

| Method | Key parameters | Returns |
|--------|----------------|---------|
| `client.search(q, limit=10)` | fuzzy match on symbol or company name | `list[StockSummary]` |
| `client.movers(category="gainers", limit=20)` | `category` = `gainers`\|`losers`\|`active` | `list[MoverItem]` |
| `client.price_shockers(min_change_pct=5.0, direction="both", limit=50)` | `direction` = `up`\|`down`\|`both` | `list[PriceShockerItem]` |
| `client.status()` | no auth required | `StatusReport` (`.status`, `.checks`, `.is_operational`) |

## 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
