DhanHQ — Ruby Client for Dhan API v2
A production-grade Ruby SDK for the Dhan trading API — ORM-like models, WebSocket market feeds, and battle-tested reliability for real trading.
🚀 60-Second Quick Start
# Gemfile
gem 'DhanHQ'require 'dhan_hq'
DhanHQ.configure do |c|
c.client_id = ENV["DHAN_CLIENT_ID"]
c.access_token = ENV["DHAN_ACCESS_TOKEN"]
end
# You're live
positions = DhanHQ::Models::Position.all
holdings = DhanHQ::Models::Holding.allWhy DhanHQ?
You could wire up Faraday and parse JSON yourself. Here's why you shouldn't:
| You get | Instead of |
|---|---|
ActiveModel-style find, all, save, cancel
|
Manual HTTP + JSON wrangling |
| Typed models with validations | Hash soup and runtime surprises |
| Auto token refresh + retry-on-401 | Silent auth failures at 3 AM |
| WebSocket reconnection with backoff | Dropped connections during volatile moves |
| 429 rate-limit cool-off | Getting banned by the exchange |
| Thread-safe, secure logging | Leaked tokens in production logs |
✨ Key Features
-
ActiveRecord-style models —
find,all,where,save,cancelacross Orders, Positions, Holdings, Funds, and more - Auto token refresh — 401 retry with fresh token via provider callback
- Thread-safe WebSocket client — Orders, Market Feed, Market Depth with auto-reconnect
- Exponential backoff + 429 cool-off — no manual rate-limit management
- Secure logging — automatic token sanitization in all log output
- Super Orders — entry + stop-loss + target + trailing jump in one request
-
Instrument convenience methods —
.ltp,.ohlc,.option_chaindirectly on instruments - Full REST coverage — Orders, Trades, Forever Orders, Super Orders, Positions, Holdings, Funds, HistoricalData, OptionChain, MarketFeed, EDIS, Kill Switch, P&L Exit, Alert Orders, Margin Calculator
- P&L Based Exit — automatic position exit on profit/loss thresholds
-
Postback parser — parse Dhan webhook payloads with
Postback.parseand status predicates - EDIS model — ORM-style T-PIN, form, and status inquiry for delivery instruction slips
Installation
# Gemfile (recommended)
gem 'DhanHQ'bundle install
# or
gem install DhanHQBleeding edge? Use
gem 'DhanHQ', git: 'https://github.com/shubhamtaywade82/dhanhq-client.git', branch: 'main'only if you need unreleased features.
⚠️ Breaking Change (v2.1.5+)
The require statement changed:
# Before # Now
require 'DhanHQ' → require 'dhan_hq'The gem name in your Gemfile stays DhanHQ — only the require changes.
Configuration
Static token (simplest)
require 'dhan_hq'
DhanHQ.configure_with_env # reads DHAN_CLIENT_ID + DHAN_ACCESS_TOKEN from ENV| Variable | Purpose |
|---|---|
DHAN_CLIENT_ID |
Your Dhan trading account client ID |
DHAN_ACCESS_TOKEN |
API token from the Dhan console |
Dynamic token (production / OAuth)
DhanHQ.configure do |config|
config.client_id = ENV["DHAN_CLIENT_ID"]
config.access_token_provider = -> { YourTokenStore.active_token }
config.on_token_expired = ->(error) { YourTokenStore.refresh! } # optional
endWhen the API returns 401, the client retries once with a fresh token from your provider.
Full details: TOTP flows, partner mode, token endpoint bootstrap, auto-management — see docs/AUTHENTICATION.md.
REST API
Orders — Place, Modify, Cancel
order = DhanHQ::Models::Order.new(
transaction_type: DhanHQ::Constants::TransactionType::BUY,
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
product_type: DhanHQ::Constants::ProductType::MARGIN,
order_type: DhanHQ::Constants::OrderType::LIMIT,
validity: DhanHQ::Constants::Validity::DAY,
security_id: "43492",
quantity: 50,
price: 100.0
)
order.save # places the order
order.modify(price: 101.5)
order.cancelPositions, Holdings, Funds
DhanHQ::Models::Position.all
DhanHQ::Models::Holding.all
DhanHQ::Models::Fund.balanceHistorical Data
bars = DhanHQ::Models::HistoricalData.intraday(
security_id: "13",
exchange_segment: DhanHQ::Constants::ExchangeSegment::IDX_I,
instrument: DhanHQ::Constants::InstrumentType::INDEX,
interval: "5",
from_date: "2025-08-14",
to_date: "2025-08-18"
)Instrument Lookup
nifty = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
nifty.ltp # last traded price
nifty.ohlc # OHLC data
nifty.option_chain(expiry: "2025-02-28")
nifty.intraday(from_date: "2025-08-14", to_date: "2025-08-18", interval: "15")WebSockets
Three real-time feeds, all with auto-reconnect, backoff, 429 cool-off, and thread-safe operation.
Order Updates
DhanHQ::WS::Orders.connect do |order_update|
puts "#{order_update.order_no} → #{order_update.status} (#{order_update.traded_qty}/#{order_update.quantity})"
endMarket Feed (Ticker / Quote / Full)
client = DhanHQ::WS.connect(mode: :ticker) do |tick|
puts "#{tick[:security_id]} = ₹#{tick[:ltp]}"
end
client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13") # NIFTY
client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25") # BANKNIFTYMarket Depth
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
DhanHQ::WS::MarketDepth.connect(symbols: [
{ symbol: "RELIANCE", exchange_segment: reliance.exchange_segment, security_id: reliance.security_id }
]) do |depth|
puts "Best Bid: #{depth[:best_bid]} | Best Ask: #{depth[:best_ask]} | Spread: #{depth[:spread]}"
endCleanup
DhanHQ::WS.disconnect_all_local! # kills all local WS connectionsSuper Orders
Entry + target + stop-loss + trailing jump in a single request:
DhanHQ::Models::SuperOrder.create(
transaction_type: DhanHQ::Constants::TransactionType::BUY,
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
product_type: DhanHQ::Constants::ProductType::CNC,
order_type: DhanHQ::Constants::OrderType::LIMIT,
security_id: "11536",
quantity: 5,
price: 1500,
target_price: 1600,
stop_loss_price: 1400,
trailing_jump: 10
)Full API reference (modify, cancel, list, response schemas): docs/SUPER_ORDERS.md
Real-World Example: NIFTY Trend Monitor
require 'dhan_hq'
DhanHQ.configure_with_env
# 1. Check the trend using historical 5-min bars
bars = DhanHQ::Models::HistoricalData.intraday(
security_id: "13", exchange_segment: DhanHQ::Constants::ExchangeSegment::IDX_I,
instrument: DhanHQ::Constants::InstrumentType::INDEX, interval: "5",
from_date: Date.today.to_s, to_date: Date.today.to_s
)
closes = bars.map { |b| b[:close] }
sma_20 = closes.last(20).sum / 20.0
trend = closes.last > sma_20 ? :bullish : :bearish
puts "NIFTY trend: #{trend} (LTP: #{closes.last}, SMA20: #{sma_20.round(2)})"
# 2. Stream live ticks for real-time monitoring
client = DhanHQ::WS.connect(mode: :quote) do |tick|
puts "NIFTY ₹#{tick[:ltp]} | Vol: #{tick[:vol]} | #{Time.now.strftime('%H:%M:%S')}"
end
client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
# 3. On signal, place a super order with built-in risk management
# DhanHQ::Models::SuperOrder.create(
# transaction_type: DhanHQ::Constants::TransactionType::BUY, exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO, ...
# target_price: entry + 50, stop_loss_price: entry - 30, trailing_jump: 5
# )
# 4. Clean shutdown
at_exit { DhanHQ::WS.disconnect_all_local! }
sleep # keep the script aliveRails Integration
Need initializers, service objects, ActionCable wiring, and background workers? See the Rails Integration Guide.
📚 Documentation
| Guide | What it covers |
|---|---|
| Authentication | Token flows, TOTP, OAuth, auto-management |
| Configuration Reference | Full ENV matrix, logging, timeouts, available resources |
| WebSocket Integration | All WS types, architecture, best practices |
| WebSocket Protocol | Packet parsing, request codes, tick schema, exchange enums |
| Rails WebSocket Guide | Rails-specific patterns, ActionCable |
| Rails Integration | Initializers, service objects, workers |
| Standalone Ruby Guide | Scripts, daemons, CLI tools |
| Super Orders API | Full REST reference for super orders |
| API Constants Reference | All valid enums, exchange segments, and order parameters |
| Data API Parameters | Historical data, option chain parameters |
| Testing Guide | WebSocket testing, model testing, console helpers |
| Technical Analysis | Indicators, multi-timeframe aggregation |
| Troubleshooting | 429 errors, reconnect, auth issues, debug logging |
| Release Guide | Versioning, publishing, changelog |
Best Practices
- Keep
on(:tick)handlers non-blocking — push heavy work to a queue/thread - Use
mode: :quotefor most strategies;:fullonly if you need depth/OI - Don't exceed 100 instruments per subscribe frame (auto-chunked by the client)
- Call
DhanHQ::WS.disconnect_all_local!on shutdown - Avoid rapid connect/disconnect loops — the client already backs off on 429
Contributing
PRs welcome! Please include tests for new features. See CHANGELOG.md for recent changes.
bundle exec rake # run tests
bundle exec rubocop # lint
bin/console # interactive console