# Getting started

`sahiapi` is the official Python client for the [Sahi APIs](/). It gives you
one class, `SahiAPI`, for every REST endpoint, and `SahiFeed` for the live
market-data and order-update streams. The SDK signs in for you, keeps the
streams connected, and returns each endpoint's JSON as a Python `dict`, so the
API reference's fields apply unchanged. Any error comes back as an exception
you can catch.

## How to work through this

1. [Install](#install) the SDK and set up your [credentials](#credentials): an
   API key approved for today, and the IP you will place orders from.
2. Run the samples below in order. Each builds on the one before it, from your
   first call to placing and cancelling an order on your real account.
3. Look up the methods you need in the reference pages under
   [Where next](#where-next). New to trading? See [Trading terms](#trading-terms).

## Install

The SDK needs Python 3.9 or newer. Install it from PyPI:

```bash
pip install sahiapi
```

## Credentials

The SDK signs in with an API key. You set the key up once in the API
Dashboard; there is no API call for these steps.
[Authentication](/sdk/python/authentication/) has the details.

1. **Create an API key** with the scopes your code needs, such as
   `orders.write` to place orders or `marketdata.read` for quotes. Copy the
   secret when it is shown: it is shown only once.
2. **Approve the key** for each trading day. Creating a key approves it for
   that day, and approval lapses at 06:00 IST.
3. **Register your static IP** if you will place, modify or cancel orders.
   Order writes from any other address are refused; reads and streams need no
   registered IP.

## Your first call

`SahiAPI.from_credentials` exchanges your key id and secret for an access
token and returns a client that sends it on every call. Replace `pk_...` and
`sahi_sk_...` with your own. The token lasts until 06:00 IST, so a long-running
process signs in once a day.

```python
from sahiapi import SahiAPI

api = SahiAPI.from_credentials(
    key_id="pk_...",
    secret="sahi_sk_...",
)

print(api.get_profile())
```

## Resolve an instrument

Orders name an instrument by `exchange_segment` and `symbol`; market data and
the tick stream name it by `instrument_id`. Both come from the
[scrip master](/sdk/python/scrip-master/), which the SDK downloads on first use
and indexes for you. A lookup by symbol returns a list, so take the first match.

```python
instrument_id = api.get_instruments_by_symbol("RELIANCE", SahiAPI.EXCHANGE_SEGMENT_NSE)[0]["id"]
```

## Read the market

Every price is in paise: divide by 100 for rupees. Market-data results are
keyed by the instrument id as a string.

```python
quote = api.get_quotes(instrument_id)["data"][str(instrument_id)]
print(quote["last_trade_price"] / 100, "INR")
```

For prices you follow continuously, subscribe on the tick stream instead of
polling. Ticks reach your callback while `feed.consume()` runs. It blocks, so
run this sample on its own rather than before the order sample below. Outside
[market hours](#market-hours) you get one tick with the last price, then
nothing until the next session.

```python
from sahiapi import SahiFeed

def on_tick(data, meta):
    print(data)

feed = SahiFeed(api)
feed.subscribe_ltp([instrument_id], on_data_received=on_tick)
feed.consume()  # blocks; stop with Ctrl+C or feed.stop() from another thread
```

## Place and cancel an order

This sample places a real order on your account. It sizes the buy to what
your delivery funds cover, places a limit at the bottom of the day's
[price band](#trading-terms) so it rests on the book, and cancels it if it has
not filled. Run it during [market hours](#market-hours): outside them a regular
order is refused.

```python
import time

price = quote["dpr_lower"]  # paise
available = api.get_funds()["available"]["stocks_delivery"]  # paise
qty = min(available // price, 1)  # what the funds cover, at most 1 share
if qty < 1:
    raise SystemExit("not enough funds for one share")

result = api.place_order(
    exchange_segment=SahiAPI.EXCHANGE_SEGMENT_NSE,
    symbol="RELIANCE",
    txn_type=SahiAPI.TXN_TYPE_BUY,
    qty=qty,
    order_type=SahiAPI.ORDER_TYPE_LIMIT,
    product=SahiAPI.PRODUCT_DELIVERY,
    validity=SahiAPI.VALIDITY_DAY,
    price=price,
)
order_id = result["order_ids"][0]

time.sleep(1)  # the order takes a moment to reach the book
status = api.get_order_detail(order_id)["status"]
print(status)

if status in (SahiAPI.ORDER_STATUS_OPEN, SahiAPI.ORDER_STATUS_OPEN_PARTIAL):
    api.cancel_order(order_id)  # not filled yet
```

Raise the `1` to buy as many shares as the funds cover.

An order id means the order was accepted, not that it reached the exchange.
Some checks, such as tick size and price band, run after acceptance, so read
the order's `status` before relying on it: a refused order shows `Rejected`
with the reason in `rejection_reason`. If `place_order` raises
`SahiIpNotAllowedException`, this machine's IP is not the one you registered.

## Where next

- [Authentication](/sdk/python/authentication/): sharing a token across
  processes, narrowing scopes, and the daily token.
- [Orders](/sdk/python/orders/), [Portfolio](/sdk/python/portfolio/),
  [Funds](/sdk/python/funds/) and [Account](/sdk/python/account/): the trading
  methods.
- [Market data](/sdk/python/market-data/) and
  [Scrip master](/sdk/python/scrip-master/): quotes, candles, the option chain
  and instrument lookups.
- [Live streams](/sdk/python/streams/): `SahiFeed` for ticks and order updates.
- [Errors](/sdk/python/errors/): every exception and when it is raised.

## Getting help

If the SDK or the API does not behave as these pages say, write to
[developers@sahi.com](mailto:developers@sahi.com). Include the `request_id`
from the exception if there is one.

## Conventions

The API's [Conventions](/#conventions) hold unchanged:

- **Money is integer paise.** ₹1 is `100`, so ₹1,081.80 is `108180`.
- **Quantities are units, not lots.** One NIFTY lot of 65 is `qty=65`.
- **Timestamps are epoch milliseconds.**
- **Identifiers are strings**, except `instrument_id`, which is an integer.
- **Order writes must come from a registered IP.** Reads and streams are not
  IP-checked.
- **The SDK calls `apis.sahi.com` and `direct.sahi.com`.** If a firewall limits
  outbound traffic, allow both.

### Market hours

NSE and BSE cash and F&O trade 09:15–15:30 IST on exchange trading days. While
the market is closed, a regular order is refused; send an after-market order
(`variant=SahiAPI.VARIANT_AMO`) instead, and it goes out at the next open.

### Trading terms

| Term | Meaning |
|---|---|
| Segment | `NSE`, `BSE`: cash. `NFO`, `BFO`: futures and options. |
| Product | `C` delivery, `I` intraday, `M` F&O carry-forward, `F` MTF. |
| Order type | `LMT` limit, `MKT` market, `SL-LMT` / `SL-MKT` stop-loss at `trigger_price`. |
| LTP | Last traded price. |
| Price band | The day's allowed range, `dpr_lower` to `dpr_upper`. |
| Tick size | The smallest price step; a price must be a whole number of ticks. |
| Lot size | F&O trade in lots; `qty` must be a multiple of `lot_size`. |
| AMO | After-market order, sent at the next session's open. |

### Constants

Each code is a class attribute on `SahiAPI` and a name in `sahiapi`. A leading
`_` stands for the group's prefix. MTF product `F` has no constant.

| Group | Constants | Values |
|---|---|---|
| Exchange segment | `EXCHANGE_SEGMENT_NSE`, `_NFO`, `_BSE`, `_BFO` | `NSE`, `NFO`, `BSE`, `BFO` |
| Transaction type | `TXN_TYPE_BUY`, `TXN_TYPE_SELL` | `B`, `S` |
| Order type | `ORDER_TYPE_LIMIT`, `_MARKET`, `_SL_LIMIT`, `_SL_MARKET` | `LMT`, `MKT`, `SL-LMT`, `SL-MKT` |
| Product | `PRODUCT_DELIVERY`, `PRODUCT_INTRADAY`, `PRODUCT_NORMAL` | `C`, `I`, `M` |
| Validity | `VALIDITY_DAY`, `VALIDITY_IOC` | `DAY`, `IOC` |
| Variant | `VARIANT_REGULAR`, `VARIANT_AMO` | `REGULAR`, `AMO` |
| Order status | `ORDER_STATUS_OPEN`, `_OPEN_PARTIAL`, `_PARTIAL_FILLED`, `_COMPLETED`, `_CANCELLED`, `_REJECTED` | `Open`, `OpenPartial`, `PartialFilled`, `Completed`, `Cancelled`, `Rejected` |
| Candle size | `CANDLE_1M`, `_3M`, `_5M`, `_15M`, `_30M`, `_1H`, `_1D` | `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `1d` |
| Tick mode | `MODE_LTP`, `MODE_QUOTE`, `MODE_DEPTH` | `LTP`, `QUOTE`, `DEPTH` |
| Scope | `SCOPE_ORDERS_WRITE`, `_ORDERS_READ`, `_POSITIONS_READ`, `_HOLDINGS_READ`, `_FUNDS_READ`, `_PROFILE_READ`, `_MARKETDATA_READ` | `orders.write`, `orders.read`, `positions.read`, `holdings.read`, `funds.read`, `profile.read`, `marketdata.read` |
| WebSocket close code | `WS_CLOSE_AUTH_EXPIRED`, `_FORBIDDEN`, `_HEARTBEAT`, `_DROPPED`, `_TOO_MANY` | `4401`, `4403`, `4408`, `4409`, `4429` |
