Fyers (Indian broker) adapter for NautilusTrader — REST + WebSocket bridge for NSE/BSE live data and execution
Project description
nautilus-fyers
A NautilusTrader-style adapter for Fyers (Indian broker, API v3), bridging Fyers REST + WebSocket APIs for live market data and execution on NSE/BSE instruments.
Built following the phased plan in fyers_nautilus_adapter_plan.md.
Architecture
Python layer nautilus_fyers/ framework-neutral clients, config, auth
Rust core crates/adapters/fyers/ wire models, enums, parsing, protocol state
The Python layer is fully functional standalone (stdlib only; websockets needed for live
streaming). The Rust crate mirrors the NautilusTrader adapter crate layout (models/enums/parse
per module, no async runtime dependency) so it can later be vendored into the
nautilus_trader workspace and bound via PyO3.
| Component | Module | Status |
|---|---|---|
| Instrument provider (symbol master, positional + header CSV) | providers.py |
✅ tested |
| OAuth2 auth flow + daily token refresh | auth.py |
✅ tested |
| REST client (quotes, depth, history, orders, positions, holdings, funds, tradebook) | http.py |
✅ tested |
| Rate limiting (token bucket, 10 req/s) + retries with backoff | http.py |
✅ tested |
Real-time streaming (official SDK decoder, asyncio bridge, [stream] extra) |
streaming.py, data.py |
✅ live-verified |
| Data socket protocol state (modes, reconnect + subscription restore) | transport.py, ws.py |
✅ tested |
| Order socket (order/trade/position updates, replay dedup) | transport.py, execution.py |
✅ tested |
| Order lifecycle (submit/modify/cancel/cancel-all) + reconciliation reports | execution.py |
✅ tested |
| NautilusTrader TradingNode integration (factories, live clients, Equity/Option/Future instruments, reconciliation reports, account state) | nautilus.py |
✅ tested against nautilus_trader 1.230 |
| Rust core (models, enums, parse, protocol state) | crates/adapters/fyers |
✅ tested |
Quick start
1. Get an access token (daily)
export FYERS_CLIENT_ID="ABCD1234-100"
export FYERS_SECRET_KEY="..."
python examples/generate_access_token.py
export FYERS_ACCESS_TOKEN="<printed token>"
2. Stream live quotes
pip install "nautilus-fyers[stream]" # real-time streaming (official SDK decoder)
python examples/basic_data_subscription.py
from nautilus_fyers import FyersDataClientConfig, QuoteEvent, create_data_client
client = create_data_client(FyersDataClientConfig.from_env())
client.on(QuoteEvent, my_async_handler)
await client.subscribe_quotes(["NSE:RELIANCE-EQ"])
await client.connect()
3. Trade
from nautilus_fyers import FyersExecClientConfig, create_execution_client
exec_client = create_execution_client(FyersExecClientConfig.from_env())
await exec_client.connect() # order socket: real-time order/trade/position updates
response = await exec_client.submit_order(
"NSE:SBIN-EQ", "BUY", 1, order_type="LIMIT", limit_price=795.0,
)
await exec_client.cancel_order(response["id"])
state = await exec_client.reconcile() # orders + positions + trades
See examples/ for complete scripts, including a safe live order-lifecycle test that uses a far-from-market limit order (Fyers has no paper-trading sandbox).
4. Run a NautilusTrader TradingNode
pip install "nautilus-fyers[nautilus,live]"
from nautilus_trader.config import TradingNodeConfig
from nautilus_trader.live.node import TradingNode
from nautilus_fyers.nautilus import (
FyersDataClientConfig, FyersExecClientConfig,
FyersLiveDataClientFactory, FyersLiveExecClientFactory,
)
config = TradingNodeConfig(
trader_id="FYERS-TRADER-001",
data_clients={"FYERS": FyersDataClientConfig(client_id=..., access_token=...)},
exec_clients={"FYERS": FyersExecClientConfig(client_id=..., access_token=...)},
)
node = TradingNode(config=config)
node.add_data_client_factory("FYERS", FyersLiveDataClientFactory)
node.add_exec_client_factory("FYERS", FyersLiveExecClientFactory)
node.build()
node.run()
Strategies address instruments as NSE:SBIN-EQ.FYERS. Execution updates
(accepted/filled/cancelled/rejected, partial fills, account state, reconciliation
reports) flow through the real-time order socket; quote ticks are synthesized by
REST polling (configurable quote_poll_interval, default 1s) until the binary
HSM market-data codec is implemented.
→ Full guides:
- docs/nautilus_integration.md — live trading: instrument loading, order-type mapping, product types, restarts/reconciliation, and a complete SMA crossover strategy (examples/sma_crossover_live.py)
- docs/backtesting.md — backtesting: Fyers history →
Nautilus
Bars →BacktestEngine, with history-endpoint limits and a validated runnable example (examples/backtest_sma.py)
5. Load instruments
from nautilus_fyers import FyersInstrumentProvider
provider = FyersInstrumentProvider()
await provider.load_all_async({"segment": ["CM", "FO"]}) # downloads the daily symbol master
instrument = provider.get("NSE:RELIANCE-EQ.FYERS")
Development
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest # Python tests
cargo test --manifest-path crates/adapters/fyers/Cargo.toml # Rust tests
All test fixtures live under test_data/ and mirror real Fyers API payload shapes
(http_*.json for REST, ws_*.json for WebSocket frames, plus a positional symbol-master CSV).
Implementation notes & deviations from the plan
- Authorization header: Fyers v3 expects
Authorization: <client_id>:<access_token>, notBearer <token>as sketched in the plan.FyersConfig.authorization()builds the correct form. - Modify order uses HTTP
PATCH(matching the officialfyers-apiv3SDK), notPUT. - Endpoint paths: trading endpoints live under
/api/v3/...and market data under/data/...onapi-t1.fyers.in; the plan's single/orders,/data/quotespaths were normalized accordingly. - WebSocket protocol (verified against the official
fyers-apiv3SDK source): auth travels on the websocket handshake as anauthorization: <client_id>:<token>header — there is no JSON "CONNECT" frame. The order socket iswss://socket.fyers.in/trade/v3(JSON), the data socket iswss://socket.fyers.in/hsm/v1-5/prod(binary "HSM" protocol that first requires symbol→HSM-token conversion via/data/symbol-token), and keepalive is a literal"ping"text frame. Raw order-socket frames use short field names (qty_filled,price_limit,tran_side, ...) which the event parsers accept alongside the mapped names. - Binary data frames (HSM): solved by wrapping the official
fyers-apiv3SDK — the broker's own maintained decoder — as an optional streaming backend (pip install "nautilus-fyers[stream]"), the same pattern NautilusTrader's IBKR adapter uses withibapi. With the extra installed,subscribe_quotes()delivers true streaming ticks; without it, quotes fall back to 1s REST polling. A native asyncio HSM codec remains a possible future replacement. - Native Nautilus objects: NautilusTrader is an optional dependency. The core adapter
emits neutral event dataclasses (
QuoteEvent,OrderEvent, ...);nautilus_fyers.nautilus(imported only when NautilusTrader is installed) provides fullTradingNodewiring —LiveMarketDataClient/LiveExecutionClientsubclasses, Equity/Option/Future instrument conversion, order lifecycle events with partial-fill deltas, reconciliation reports, and account state from/funds. - Rust core: the crate contains the domain layer (models, enums, parsing, subscription
and dedup state) with fixture-driven unit tests, but no HTTP/WebSocket I/O runtime — that
arrives when the crate is vendored into the NautilusTrader workspace where
nautilus-networkprovides the clients.
Restarts & carryforward
Order ownership survives restarts through three mechanisms:
- Every order is submitted with a Fyers
orderTagcarrying a sanitized copy of the NautilusClientOrderId, and the tag round-trips in order-book rows and socket frames. - On connect, the execution client seeds its venue-order map and cumulative fill counters from the venue order book — so socket replays of already-filled orders never double-fill, and fills that happened while you were offline reconcile as deltas.
- Reconciliation reports resolve
ClientOrderIdfrom the returned tag by matching against cached orders.
For step 3 to work across a process restart, enable Nautilus cache persistence so cached orders survive (otherwise recovered orders are adopted as external/venue orders — safe, but not attributed to your strategy):
from nautilus_trader.config import CacheConfig, DatabaseConfig
config = TradingNodeConfig(
...,
cache=CacheConfig(database=DatabaseConfig(type="redis", host="localhost", port=6379)),
)
Positions carried overnight (CNC) are recovered via position status reports on every start.
Symbol format
Fyers symbols look like NSE:RELIANCE-EQ, NSE:NIFTY25JUL25000CE, BSE:HDFCBANK-A.
Instrument ids append the venue: NSE:RELIANCE-EQ.FYERS.
Rate limits & tokens
- REST: ~10 requests/second (enforced client-side by a token bucket).
- Access tokens expire daily (~6 AM IST). Use
TokenManagerwith a refresh callback, or re-runexamples/generate_access_token.py. Refresh tokens last ~15 days.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nautilus_fyers-0.3.0.tar.gz.
File metadata
- Download URL: nautilus_fyers-0.3.0.tar.gz
- Upload date:
- Size: 44.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0397cb32aa496bbf00faba698d941939503d5ae7b769e82c30e29973adc55b10
|
|
| MD5 |
226c7bd27dafe62a247ef91bf8989f11
|
|
| BLAKE2b-256 |
874d81495df863466cfe00307cf49ad256e3b6425fccfdd90a5aefe61d24ec37
|
File details
Details for the file nautilus_fyers-0.3.0-py3-none-any.whl.
File metadata
- Download URL: nautilus_fyers-0.3.0-py3-none-any.whl
- Upload date:
- Size: 36.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c258c68d6e5ef89f8e13581766c116a1e161c2661b75eb06d5016787f10f2274
|
|
| MD5 |
c4d9148629de894e9fe14ace72d8c590
|
|
| BLAKE2b-256 |
af617ab2ed9efafdadf31c7e7f09035a6a1bdb78c9dfef67f23be71a9a66b61e
|