GreekSoft API
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 throughdata_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
pip install greeksoft
Quick Start
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 below).
Dependencies Used by greeksoft.py
requestspandaswebsocket-clientorjson(optional, auto-detected if installed)ujson
Import
from greeksoft import GreekAPI
Initialize
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 returngcid— 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
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.
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_listis also subscribed on the rawbinaryDatafeed, 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():
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:
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'— decodedbinaryIndexpacket (index value, high/low, % change,lIndexCode, ...).packet_type: 'depth'— decodedbinaryData(MBP) packet: 5 bid + 5 ask levels, LTP, volume, OHLC,lToken, pluslIndicativeImbalanceQtyandlLimitImbalanceQty(andcasrefpriceon 278-byte packets).packet_type: 'oi'— decodedOpenInterest-family packet. Two genuinely different structures, both reported under this samepacket_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,iGreekCode5067) — fields:iGreekCode,lLogTime,iMarketType,lOurToken,lCurrentOi(Open Interest),lToken,iMarketId. The meaning ofiMarketTypevalues is undocumented. - 58 bytes, NSE (
decode_binary_oi,GREEK7202,iGreekCode5002) — layout per the official spec.
- 26 bytes, BSE (
packet_type: 'stats'— decoded 36-byte packet. OnlylOurTokenis verified; fields suffixed_unconfirmedhave 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_messageenqueues 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
quote = api.token_broadcast("102000000", "option")
print(quote)
Multiple token quote
quotes = api.multiple_token_broadcast("option", "NSE", ["102000000", "102000001"])
print(quotes)
Market depth (MBP)
depth = api.MBP_data("NSE", ["102000000", "102000001"])
print(depth)
Server time
print(api.server_time()) # e.g. '25-08-2026 10:30:00' (IST)
Margin calculation
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
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)
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.
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
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
api.subscribe_token(["102000000", "102000001"])
api.unsubscribe_token("102000000")
Shutdown
api.close_connection()
Notes
Authorizationis handled internally via session token, fetched fresh on each client construction.- If
procli != "1",AccountNumberis not sent in order payload. - Current implementation keeps
self.ssl_verify = False. data_stream()andorder_stream()are blocking generators backed by buffered deques.
License
MIT License (see LICENSE).
Metadata
Release files for greeksoft 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| greeksoft-0.1.2.tar.gz | 31.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| greeksoft-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 56.4 kB
Release files / greeksoft-0.1.2.tar.gz
| Download URL | greeksoft-0.1.2.tar.gz |
|---|---|
| Size | 31.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
310a99010b097fbb3c48717b688ecaaddde1934bf31e668a9ed03ec9e9c550bc
|
|
BLAKE2b-256 checksum How to use checksums |
6b7db026f030933d9344c399803e47143ff683fa7c797ed5a6a374593b91fabd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.0
|
Release files / greeksoft-0.1.2-py3-none-any.whl
| Download URL | greeksoft-0.1.2-py3-none-any.whl |
|---|---|
| Size | 25.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
853f971dd3092f82398c6c6311a07f68385846548da3a64ba1389fc8c91e56d0
|
|
BLAKE2b-256 checksum How to use checksums |
8c00cf202847e688391327c6a55408376a88ca16f6d9a374125e87cdd8061b45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.0
|
Security for greeksoft 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.