Indian broker adapters for Honba, the AI-native trading and research platform.
Each broker is an independent Python package in this repository. A package implements
the core adapter contract from honba.adapters — a lifecycle facade plus a market-data
role and an execution role (ADR 0010) — and
advertises itself through the honba.adapters entry-point group. The engine discovers an
adapter by name and never imports a broker SDK itself.
| Package | Broker | Auth | Status |
|---|---|---|---|
honba-dhan |
DhanHQ | Access token | Implemented: execution, market data, streaming, contract-certified |
honba-zerodha |
Zerodha Kite Connect v3 | OAuth (daily) | Implemented: execution, market data, KiteTicker, contract-certified |
honba-angelone |
Angel One SmartAPI | TOTP + API key | Package scaffold only |
honba-fyers |
Fyers API v3 | OAuth | Package scaffold only |
honba-upstox |
Upstox Uplink | OAuth (daily) | Package scaffold only |
honba-kotak |
Kotak Neo | OAuth | Package scaffold only |
honba-iifl |
IIFL Markets | — | Package scaffold only |
honba-motilal |
Motilal Oswal | — | Package scaffold only |
honba-adapters-shared |
— | — | Shared infrastructure for every adapter above |
Only dhan and zerodha are registered through the honba.adapters entry point today.
The remaining packages have the uniform layout in place but no implementation yet.
honba-adapters/
├── shared/ honba-adapters-shared: contract suite, boundary check,
│ fixtures, rate limiting, TOTP auth, websocket transport
├── dhan/ honba-dhan: src/honba_dhan/, tests/
├── zerodha/ honba-zerodha: src/honba_zerodha/, tests/
├── angelone/ … motilal/ uniform package skeletons (src/honba_<broker>/)
├── configs/ paper/live TOML for dhan and zerodha
├── examples/ runnable adapter examples
└── scripts/smoke_test.py guarded live/paper smoke test runner
Every adapter follows the same internal structure: config.py (config dataclass),
data.py (market-data role), execution.py (execution role), instruments.py,
websocket.py, http.py, parsing.py and constants.py. The facade and both roles are
satisfied by one object, e.g. honba_dhan.DhanAdapter.
The contract lives in the core, not here:
Adapter— identity (name),capabilities(),connect(),disconnect(),session(),is_connected(), plus therequire_connected()/require_capabilities()guards.MarketDataAdapter—instruments,search_instruments,quote,depth,historical_bars,subscribe,unsubscribe.ExecutionAdapter—place_order,modify_order,cancel_order,cancel_all,order_status,orders,trades,positions,holdings,funds,margin.
Two rules matter most: an unsupported request raises CapabilityError before any
network call, and a broker refusal comes back as an OrderReport with
status=REJECTED, never as an exception. See
Adapter Interface for the
full surface and value types.
honba-adapters-shared packages the pieces every adapter reuses:
assert_adapter_contract(adapter)— runs the behavioural contract suite (honba.adapters.contract) and raisesContractViolationon the first broken rule.find_boundary_violations— keeps broker wire types inside their adapter.load_fixture/fixtures_dir— read recorded broker responses fromtests/fixtures/.RateLimiter/TokenBucket/rate_limited— token-bucket rate limiting with an injectable clock.TOTP/TokenManager/FileTokenStore— RFC 6238 TOTP and token management.WireMap/assert_covers— one-dict honba-to-wire mappings (reverse derived, duplicates rejected) and a check that every declared capability has a mapping.ReconnectingWebSocketClient/SequenceTracker— reconnecting transport with gap detection.
The repo is a collection of editable packages; install them into a virtualenv alongside
the core honba package from the sibling checkout.
cd honba-adapters
python -m venv .venv && source .venv/bin/activate
pip install -e ../honba/python # core: honba.adapters.*
pip install -e "shared[dev]" # contract suite + dev deps
pip install -e dhan -e zerodha # the implemented adaptersCredentials come from the environment, one prefix per adapter (DHAN_, ZERODHA_).
<PREFIX>MODE is live (the default) or paper.
| Adapter | Variables (<PREFIX> = DHAN_ / ZERODHA_) |
|---|---|
| dhan | <PREFIX>CLIENT_ID, <PREFIX>ACCESS_TOKEN, <PREFIX>MODE |
| zerodha | <PREFIX>API_KEY, <PREFIX>ACCESS_TOKEN, <PREFIX>API_SECRET (optional), <PREFIX>USER_ID (optional), <PREFIX>MODE |
XConfig.from_env(prefix="DHAN_")reads that set; pass another prefix to run several accounts side by side.- Credentials are required.
DhanAdapter()/ZerodhaAdapter()with no config and no credentials in the environment raisesAuthenticationErrornaming the missing variables. For simulation ask for it explicitly:mode="paper",XConfig.paper()or<PREFIX>MODE=paper(paper mode needs no credentials and uses an in-memory transport). - Declared capabilities (exchanges, products, order types, time in force, features) are
tabulated in each adapter's README.
OrderType.TRAILING_STOPis opt-in and no adapter lists it today; see Trailing stop support. HONBA_CACHE_DIRmoves the cache root; each adapter uses$HONBA_CACHE_DIR/<broker>(default~/.cache/honba/<broker>).
OrderType.TRAILING_STOP (with trail_amount or trail_percent) is only declared where a
broker documents a native, server-side trailing stop for a standalone order, with an
unambiguous mapping of the trail distance. Reviewed against published docs (Oct 2026):
| Adapter | Native feature | Source | Decision | Reason |
|---|---|---|---|---|
| dhan | Super Order trailingJump (absolute price jump) |
https://dhanhq.co/docs/v2/super-order/ | excluded | Only inside a Super Order (entry + mandatory target + SL legs), not a standalone trailing stop; docs do not state the trail is server-side; no tick/min rules; absolute only (no percent) |
| zerodha | Trailing SL on GTT, Kite Web only | https://support.zerodha.com/category/trading-and-markets/charts-and-orders/gtt/articles/trailing-stop-loss | excluded | Not exposed in Kite Connect API (announced as future) |
| angelone | trailingStopLoss on ROBO (bracket) orders |
https://smartapi.angelone.in/topic/4547/robo-trailing-stop-loss-not-being-honored | excluded | Bracket-only (needs squareoff + stoploss); developers report it is not honoured; units undocumented |
| fyers | Smart Trail TSL on platform | https://support.fyers.in/portal/en/kb/articles/how-do-i-place-a-trailing-stop-loss-smart-trail-order-in-fyers | excluded | No trailing parameter in the published API v3 order parameters (https://api-connect-docs.fyers.in/docs/parameters) |
| upstox | GTT v3 trailing_gap on stoploss rule |
https://community.upstox.com/t/api-gtt-trailing-stop-loss-order-issue/13080 | excluded | GTT, not a regular order; units undocumented; users report it behaves as a plain stop loss; adapter has no GTT path |
| kotak | Trailing SL on Bracket orders (platform) | https://www.kotakneo.com/support/how-can-i-set-a-trailing-stoploss/ | excluded | Bracket-only; no API parameter documented |
| iifl | none found | - | excluded | No trailing stop documented in the public API |
| motilal | none found | - | excluded | No trailing stop documented in the public API (advanced orders are strategy-layer) |
Excluded adapters refuse TRAILING_STOP through pre-flight capability validation. Revisit
when a broker documents a standalone server-side trailing order.
Paper mode needs no credentials: the adapter installs an in-memory mock transport.
import asyncio
from honba.adapters import available_adapters, resolve_adapter
from honba.domain.instrument import InstrumentId
async def main() -> None:
print(available_adapters()) # ['dhan', 'zerodha', ...]
adapter = resolve_adapter("zerodha", mode="paper") # or mode="live" with credentials
await adapter.connect()
try:
quote = await adapter.quote(InstrumentId("RELIANCE", "NSE"))
print(quote.bid_price, quote.ask_price)
finally:
await adapter.disconnect()
asyncio.run(main())A run config selects the adapter by name, and the engine resolves it with
resolve_adapter(name, **config); configs/ holds the paper and live TOML templates:
[adapter]
name = "dhan"
mode = "paper"
client_id = "${DHAN_CLIENT_ID}"
access_token = "${DHAN_ACCESS_TOKEN}"The default run is deterministic and offline.
python -m pytest # shared + dhan + zerodha (pytest.ini testpaths)
python -m pytest shared/tests
python -m pytest dhan/tests zerodha/testsDhan additionally has live, read-only smoke tests. They are marked live and skip
themselves unless both environment variables are set; a conftest guard refuses any
request that is not a documented read endpoint, so they cannot place an order.
export DHAN_CLIENT_ID=... DHAN_ACCESS_TOKEN=...
python -m pytest -m live dhan/tests/live -vLint with ruff check . and ruff format --check . (line length 100).
An adapter is certified when test_contract_suite[<name>] passes:
python -m pytest tests/test_contract_suite.py -vThe suite runs honba.adapters.contract.verify_adapter_contract over every package that has
tests/fixtures/<name>/routes.toml. Read endpoints are replayed from recorded responses in
that directory; the stateful order lifecycle uses the adapter's in-memory mock handler. Packages
without a routes.toml (the six scaffolds) are discovered automatically and reported as
skipped placeholders. Needs Python 3.11+ (tomllib).
scripts/smoke_test.py drives a full connect → funds → books → quote/depth → order
lifecycle → disconnect against an adapter. It defaults to paper and refuses live mode
unless HONBA_LIVE_SMOKE_TEST=1 is set (and even then places no order unless
--place-live-order is passed).
python scripts/smoke_test.py --adapter dhan --mode paper
HONBA_LIVE_SMOKE_TEST=1 python scripts/smoke_test.py --adapter dhan --mode live \
--client-id "$DHAN_CLIENT_ID" --access-token "$DHAN_ACCESS_TOKEN"examples/ contains runnable, registry-driven scripts:
01_connect_dhan.py— connect through the registry and print session + capabilities.04_place_order_paper.py— place, repeat (idempotent), rest/cancel and reject orders, then read the books back.
Both default to the in-memory fake adapter, so they run with no credentials. Pass
--adapter dhan or --adapter zerodha to drive a real package.
Zerodha. Time in force is DAY and IOC only — GTC, FOK and GTD are rejected
with CapabilityError before any network call, as are fractional quantities. Order tags
are ASCII alphanumeric, at most 20 characters. Ambiguous submits are reconciled by tag on
the next call. Full behaviour is in
honba-docs/.../india/brokers.md.
Start from dhan/ as the template, then:
- Implement the facade and both roles in
<name>/src/honba_<name>/, declaring exactly what the broker supports inAdapterCapabilities. - Build the honba-to-wire tables with
honba_adapters_shared.WireMapand callassert_covers(capabilities, order_types=..., products=..., tif=...)at import, so a declared capability without a mapping fails immediately instead of at order time. - Declare the entry point in
<name>/pyproject.toml:[project.entry-points."honba.adapters"] mybroker = "honba_mybroker:MyBrokerAdapter"
- Record read responses in
tests/fixtures/<name>/and addroutes.tomlthere (adapter, config and fallbackmodule:attrhooks plus the route-to-file map). The contract suite picks the package up with no edit totests/test_contract_suite.py. - Add the package to
pytest.ini(pythonpath,testpaths),.github/workflows/ci.yml(pip install -e) and the install line above.
Details are in MIGRATION.md and Adding Broker Adapters.
Every package depends on bare honba, which is not on PyPI and needs its Rust
extension built with maturin. CI (.github/workflows/ci.yml) therefore checks out
honba-labs/honba at the ref in the workflow's HONBA_REF env var (currently main,
so the adapters are tested against current core), runs maturin develop, installs
shared, dhan and zerodha editable, then runs ruff and the full pytest suite from
the repo root (shared, the root contract suite, dhan, zerodha). Live broker tests
skip unless credentials are set. If the adapter contract in honba changes
incompatibly, pin HONBA_REF to a tag or SHA until the adapters catch up.
Locally, with honba installed in a venv: pip install pytest pytest-asyncio duckdb yfinance cryptography && pip install -e shared -e dhan -e zerodha && pytest -q.
Apache License 2.0 — see LICENSE.