NautilusTrader adapter for Dhan + an Indian transaction-cost model (MIT, alpha, never run live)

I've been building a NautilusTrader adapter for Dhan and open-sourced it: nautilus-trader-india. MIT, installs alongside upstream, not a fork.

Before the pitch — three things I measured that are useful whether or not you ever touch my code.

1. dhanhq 2.2.0 silently drops market data. marketfeed.process_data unpacks data[0:1], dispatches on it, and returns a single process_* call. There's no loop over the header's message_length. Dhan's feed sends several packets per frame, so every packet after the first in a frame is discarded — no error, no warning, just a thinner tape than you think you have. FullDepth.process_data does chain correctly, so it's MarketFeed specifically. Worth checking if you're building on it.

2. Dhan returns HTTP 200 for failures. An unknown securityId comes back 200 with empty arrays — byte-identical to a market holiday. The status code is never the answer; the body is. Related: GET /v2/holdings returns 500 on a live account when your portfolio is empty, but 200 [] in the sandbox. If you test only against sandbox you will not find that.

3. correlationId caps at 25 characters, not the documented 30. Binary-searched it in sandbox — 25 accepted, 26 refused with DH-905 and no indication which field was wrong. Also note DH-905 is not diagnostic: I've had it back for "Invalid IP", "quantity is required", and a 90-day span limit. Classify on the message, not the code.

All of it is written up with dates and how it was measured in docs/DHAN_API_NOTES.md.

What's actually usable today

The part I'd point at first isn't broker code at all — it's nautilus_india.core, which touches no network and needs no account:

  • Indian option/future/equity instruments as stock Nautilus objects. INR, 0.05 tick, IST expiry, lot size in multiplier.
  • A transaction cost model — STT, brokerage, exchange, SEBI, stamp, GST — that reproduces Dhan's own brokerage calculator to the paisa across seven cases.
  • Lot sizes with provenance. Every value tells you whether it was observed in NSE's bhavcopy archive or collapsed from a regime.
  • A SPAN + exposure margin model, calibrated against measured figures.

One thing in there that might matter to you regardless: exercise STT. STT on an ITM option exercised at expiry is levied on intrinsic value at a rate far above the premium rate. On a small winner it can exceed the entire profit. It's the charge naive backtests omit, and Nautilus's settlement path bypasses get_commission so I can't charge it automatically — it's exposed as CostModel.exercise_cost and documented as a gap rather than hidden.

What's not done, plainly

  • It has never run against a live account. Order placement on Dhan needs a whitelisted static IP I don't have. Everything's been driven end-to-end against Dhan's sandbox.
  • No order it placed has ever filled. The sandbox has no matching engine — orders rest at PENDING forever. So fill reports and position reports have never seen real data.
  • Market data client isn't wired. The binary decoder, feed protocol and tick parsers are built and tested; the client that joins them isn't. It raises NotImplementedError on connect. Found this while writing docs and corrected the README rather than papering over it.
  • Kite adapter: not started. It's the point of the shared core, but it's fifth on the roadmap, not first.
  • Charge rates are flagged verified: false until a real contract note is checked.

Live orders need two switches — a config flag and an env var — because a stray import can't set an env var and a stray env var can't construct a client.

What I'd actually like

If you have a live Dhan account, you can settle things I can't from here. Most valuable, roughly in order: a real fill (GET /v2/trades with a row in it), a real contract note to check the charge model against, and one frame from the order-update websocket. There's an issue template for it that asks for provenance and a date.

Happy to answer anything about the Dhan API specifically — I've probed a fair bit of it.

Alpha, MIT, no warranty. It's intended to place real orders with real money and I'd read the gap table in the README before letting it.

Posted Sep 8, 2026, 11:35 AM UTC

11
Loading comments…