Metadata-Version: 2.4
Name: greeksoft
Version: 0.1.2
Summary: The official Python client for communicating with Greeksoft's CTCL Application API
Author: Greek_dev_Team
Author-email: greeksoftapi@greeksoft.co.in
License: MIT
Keywords: greeksoft,trading,algo-trading,stock-market,nse,bse,options,futures,websocket,ctcl,market-data
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Topic :: Office/Business :: Financial :: Investment
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
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.32.3
Requires-Dist: pandas>=1.5.0
Requires-Dist: websocket-client>=1.8.0
Requires-Dist: ujson>=5.10.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# GreekSoft API

[![PyPI version](https://img.shields.io/pypi/v/greeksoft.svg)](https://pypi.org/project/greeksoft/)
[![Python versions](https://img.shields.io/pypi/pyversions/greeksoft.svg)](https://pypi.org/project/greeksoft/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

`greeksoft` is a synchronous Python client for Greeksoft CTCL APIs.

It provides:
- Session authentication and login bootstrap, with nothing persisted to disk
- REST APIs for quotes, orders, positions, holdings, margins, contracts, OHLC (including a TradingView-style market-id based endpoint)
- Bulk order placement, automatically paced to the session's JWT-derived rate limit
- Apollo websocket market stream — traditional JSON feed (`data_stream()`), plus an optional **raw binary feed** for index, market-depth and open-interest data (also delivered through `data_stream()`)
- Optional IRIS websocket order-event stream
- Auto-reconnect for Apollo/IRIS (same session) on disconnect
- Fail-fast auth handling for login/password/account issues

## Install
```bash
pip install greeksoft
```

## Quick Start
```python
from greeksoft import GreekAPI

api = GreekAPI(
    user="username",
    s_pwd="session_password",
    pwd="transaction_password",
    procli="1",             # "1" retail, other values for pro
    ac_no="account_no",     # required when procli == "1"
    is_secure=False,        # True for HTTPS/WSS
    is_base_64=False,       # True when server expects base64 payloads
    rest_ip="192.168.0.1",  # server REST_IP or masked DNS
    rest_port="3000",       # server REST_PORT
    iris=True,              # True => also start the IRIS order-update stream
)

# One-shot REST call
quote = api.token_broadcast("102000000", "option")
print(quote)

# Live market-data streaming
api.start_apollo(["102000000", "102000001"])
for tick in api.data_stream():
    print(tick)
    break  # data_stream() is an infinite generator -- remove this in real usage

api.close_connection()
```
That's it — no separate login step, no manual session-token handling, and nothing else to configure. Authentication is handled internally the moment `GreekAPI(...)` is constructed (see [Credentials](#credentials) below).

## Dependencies Used by `greeksoft.py`
- `requests`
- `pandas`
- `websocket-client`
- `orjson` (optional, auto-detected if installed)
- `ujson`

## Import
```python
from greeksoft import GreekAPI
```

## Initialize
```python
api = GreekAPI(
    user="username",
    s_pwd="session_password",
    pwd="transaction_password",
    procli="1",            # "1" retail, other values for pro
    ac_no="account_no",    # required when procli == "1"
    is_secure=False,       # True for HTTPS/WSS
    is_base_64=False,      # True when server expects base64 payloads
    rest_ip="192.168.0.1", # Server REST_IP or Masked DNS
    rest_port="3000",      # Server REST_PORT
    iris=True              # True => start IRIS websocket also
)
```

## Auth Failure Behavior
- If login/auth returns known error codes (1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,18), client prints red warning text and exits (including `1`/`18`, "password expired").
- If `getlogininfo()` does not return `gcid` — client prints red warning text and exits.
- Once auth is blocked, REST calls are also blocked, and this cannot currently be cleared without reconstructing the client.

## Credentials
Nothing is stored. The client never writes your password or session token to disk — a fresh session token is requested every time `GreekAPI(...)` is constructed, and password expiry is **not** handled automatically: if the server reports it, the client prints the error and exits. Use `update_password(old, new)` yourself, then pass the new password to the constructor.


## Start Apollo Streaming
```python
token_list = ["102000000", "102000001"]
api.start_apollo(token_list)

for tick in api.data_stream():
    print(tick)
```

Each JSON tick is a merged dict: `{**data, 'oi': oi, 'bcast_time': bcast_time}`. `bcast_time` is the server's broadcast timestamp for that tick, not a client-side receive time.

## IRIS Order-Update Streaming
Requires `iris=True` at construction (and an IRIS port resolved during login). `order_stream()` is a second, independent blocking generator — only RMS-rejection order updates are delivered on it.

```python
import threading

def watch_orders():
    for update in api.order_stream():
        print(update)

threading.Thread(target=watch_orders, daemon=True).start()
```

## Binary WebSocket Feed (Index & Market Depth)
A second feed on the same Apollo socket — struct-packed binary packets instead of base64/JSON. The wire-level subscription requests (index tokens + `token_list` on the traditional JSON channel) are always sent the same way regardless of `broadcast` — but what actually reaches `data_stream()` differs sharply by mode:

- **`broadcast="JSON"` (default)** — `data_stream()` yields only JSON: the full merged dict, for both index and your subscribed tokens. No binary data.
- **`broadcast="BINARY"`** — `token_list` is *also* subscribed on the raw `binaryData` feed, and **all JSON-shaped broadcast output is suppressed entirely** — both index and your own subscribed tokens. `data_stream()` then yields *only* binary-decoded packets, no JSON at all.

Index tokens are **hardcoded** (`GreekAPI.DEFAULT_INDEX_TOKENS` — NIFTY, BANKNIFTY, FINNIFTY, BANKEX, SENSEX) and always subscribed — there's no parameter to override them, and no way to skip them, in either mode.

**Easiest way — the `broadcast` flag on `start_apollo()`:**
```python
api.start_apollo(["102000000"], broadcast="BINARY")   # 'JSON' (default) | 'BINARY'

for pkt in api.data_stream():
    print(pkt)   # {'packet_type': 'index' | 'depth' | 'oi' | 'raw', ...} -- binary only, no JSON
```
`broadcast="BINARY"` is equivalent to calling `subscribe_binary_market_data(token_list)` yourself right after `start_apollo()`, just done for you at connect time and automatically re-sent on every reconnect — plus it suppresses JSON output entirely.

**Manual, fine-grained control — works independently of the `broadcast` flag:**
```python
api.start_apollo(["102000000"])   # JSON mode (default) -- full merged-dict ticks

api.subscribe_binary_idx_tkn(["101999957"])          # additionally, index tokens on binaryIndex
api.subscribe_binary_market_data(["102000000"])      # additionally, depth/MBP tokens on binaryData

for pkt in api.data_stream():
    if isinstance(pkt, dict) and 'packet_type' in pkt:
        print("binary:", pkt)
    else:
        print("json:", pkt)
```
Manually subscribing to binary while still in `broadcast="JSON"` mode does **not** suppress JSON — you get both feeds interleaved, so check for the `'packet_type'` key to tell them apart. JSON suppression only happens automatically when `broadcast="BINARY"` was passed to `start_apollo()`.

- `packet_type: 'index'` — decoded `binaryIndex` packet (index value, high/low, % change, `lIndexCode`, ...).
- `packet_type: 'depth'` — decoded `binaryData` (MBP) packet: 5 bid + 5 ask levels, LTP, volume, OHLC, `lToken`, plus `lIndicativeImbalanceQty` and `lLimitImbalanceQty` (and `casrefprice` on 278-byte packets).
- `packet_type: 'oi'` — decoded `OpenInterest`-family packet. Two genuinely different structures, both reported under this same `packet_type` (distinguished by which keys are present, since dispatch is by exact byte length before tagging):
  - **26 bytes, BSE** (`decode_binary_oi_lite`, `GREEK_OI_BSE`, `iGreekCode` 5067) — fields: `iGreekCode`, `lLogTime`, `iMarketType`, `lOurToken`, `lCurrentOi` (Open Interest), `lToken`, `iMarketId`. The meaning of `iMarketType` values is undocumented.
  - **58 bytes, NSE** (`decode_binary_oi`, `GREEK7202`, `iGreekCode` 5002) — layout per the official spec.
- `packet_type: 'stats'` — decoded 36-byte packet. Only `lOurToken` is verified; fields suffixed `_unconfirmed` have no documented meaning and are informational only.
- `packet_type: 'raw'` — an unrecognized packet size, surfaced as `{'raw': <hex>, 'size': N}` rather than silently dropped.

Binary subscriptions are re-sent automatically after an Apollo reconnect, the same way the common subscription's tokens already are. `unsubscribe_binary_idx_tkn(token)` / `unsubscribe_binary_market_data(token_list)` remove them.

`subscribe_binary_market_data()` sends each token's real exchange **segment** alongside it (`{"token": "3918", "segment": "1"}`), looked up automatically from the contract master (`get_contract_data()`, fetched and cached lazily on first use — no manual step needed):

| ExchangeSegMent | segment |
|---|---|
| NSEEQ | 1 |
| NSEFO | 2 |
| BSEEQ | 4 |
| BSEFO | 5 |

A token not found in the contract master (or with an unrecognized segment) is still sent, just without a `segment` key, rather than guessing. `subscribe_binary_idx_tkn()` is unaffected — index codes aren't tradable contracts and don't need a segment.

## Performance Notes
- Websocket callback is lightweight (`on_message` enqueues raw payloads).
- Decoding/routing runs in a dedicated worker thread.
- Market and order outputs are stored in rolling buffers (`deque(maxlen=...)`).
- Requests use persistent HTTP session pooling (`requests.Session`) and default timeout.
- Buffer overwrite/drop counters are logged periodically when backpressure happens.

## Auto-Reconnect Behavior
- On Apollo/IRIS disconnect/crash, client retries every **5 seconds**
- Retry window is **10 minutes**
- Reconnect uses the **same session_id** (no forced re-login)
- **Apollo sends no heartbeat** — the continuous broadcast data flowing over that socket keeps it alive on its own. **Only IRIS** sends a periodic heartbeat (order updates can go quiet for long stretches with nothing to keep the connection alive otherwise).
- IRIS's heartbeat loop stops gracefully if the socket is already closed (no thread-crash traceback).

## Key REST Examples

### Single token quote
```python
quote = api.token_broadcast("102000000", "option")
print(quote)
```

### Multiple token quote
```python
quotes = api.multiple_token_broadcast("option", "NSE", ["102000000", "102000001"])
print(quotes)
```

### Market depth (MBP)
```python
depth = api.MBP_data("NSE", ["102000000", "102000001"])
print(depth)
```

### Server time
```python
print(api.server_time())   # e.g. '25-08-2026 10:30:00' (IST)
```

### Margin calculation
```python
margin = api.margin_calculator(token="102000000", ltp=2450.50, net_qty=75, buy_sell="Buy")

margin_multi = api.multi_token_margin_calc(
    token_list=["102000000", "102000001"],
    qty_list=[75, 150],
    ltp_list=[2450.50, 1200.00],
    side_list=["Buy", "Sell"],
)
```

### Place order
```python
resp = api.place_order(
    tokenno="102000000",
    symbol="RELIANCE",
    lot="1",
    qty="75",
    price="2450.50",
    buysell=1,      # 1=BUY, 2=SELL
    ordtype=1,
    trigprice=0,
    exchange="NSE",
    validity="0",
    strategyname="example_strategy"
)
print(resp)
```

### OHLC (TradingView-style, market-id based)
```python
candles = api.ohlc_tdview(
    exchange_token=2885,
    exchange_segment="NFO",   # one of: NEQ, NFO, NCD, BEQ, BFO, BCD, NCOM, MCOM, NSLBM
    start_time=1787130900,    # unix timestamp
    end_time=1787217300,
    interval=5,               # minutes
)
```
`exchange_segment` is resolved through an internal market-id lookup table; an unrecognized value raises `ValueError` before any network call is made.

### Bulk Order Placement
`place_orders_batch()` sends a list of order specs — same keyword shape as `place_order()` — automatically paced to the session's message-rate limit, which is discovered from the JWT session token with no configuration needed.

```python
order_specs = [
    dict(tokenno="102000000", symbol="NIFTY", lot=1, qty=65, price=164.95,
         buysell=1, ordtype=2, trigprice=0, exchange="NFO", validity=0,
         strategyname="bulk_example"),
    dict(tokenno="102000001", symbol="NIFTY", lot=1, qty=65, price=128.80,
         buysell=1, ordtype=2, trigprice=0, exchange="NFO", validity=0,
         strategyname="bulk_example"),
]

results = api.place_orders_batch(order_specs)   # list of (spec, response)
for spec, resp in results:
    print(spec["symbol"], "->", resp)

api.get_message_rate_limit()     # orders/sec this session is allowed to send
api.get_session_token_claims()   # full decoded JWT payload, for diagnostics
```

### Other common methods
```python
api.Order_Trade_status("gorderid")
api.all_pending_order()
api.cancel_order("gorderid")
api.modify_order(price="100", lot="1", qty="75", ordtype=1, gorderid="gorderid")

api.Orderbook_All()
api.Orderbook_Traded()
api.Orderbook_Rejected()
api.Orderbook_lite("gorderid")

api.Net_Position_request()
api.Net_position_Detailed()
# Strategy-wise net position:
# type="EXP" -> previous-day position details
# type="" (empty string) -> today's net position details
api.Net_Position_Details_strategywise("EXP")
api.Net_Position_Details_strategywise("")

api.get_margin_details()
api.get_holding_details()
api.get_contract_data()
api.get_ohlc_data(token="102000000", date="20260115", interval=1)

api.script_details_full(exchange="NSE", asset_type="equity", symbol="RELIANCE")
api.update_password(old_password="old", new_password="new")

api.option_chain()
```

## Subscription Controls
```python
api.subscribe_token(["102000000", "102000001"])
api.unsubscribe_token("102000000")
```

## Shutdown
```python
api.close_connection()
```

## Notes
- `Authorization` is handled internally via session token, fetched fresh on each client construction.
- If `procli != "1"`, `AccountNumber` is not sent in order payload.
- Current implementation keeps `self.ssl_verify = False`.
- `data_stream()` and `order_stream()` are blocking generators backed by buffered deques.

## License
MIT License (see `LICENSE`).
