DhanHQ — The Ruby SDK for Dhan API v2
Build trading systems in Ruby without fighting raw HTTP, fragile auth flows, or unreliable market streams.
DhanHQ is a production-grade Ruby SDK for the Dhan trading API, designed for:
- trading bots
- real-time market data streaming
- portfolio and order management
- Rails or standalone trading systems
If you're looking for a Ruby SDK for Dhan API, this is built to be the default choice.
Unlike thin wrappers, DhanHQ gives you:
- typed models for orders, positions, holdings, and more
- WebSocket clients with auto-reconnect and backoff
- token lifecycle management with retry-on-401
- safety rails for live trading
This is closer to trading infrastructure than a simple API client.
Install and Run in 60 Seconds
# 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 — no manual HTTP, no JSON parsing
positions = DhanHQ::Models::Position.allWho This Is For
- Ruby developers building trading bots
- Rails apps integrating the Dhan API
- Algo trading systems that need clean abstractions over raw HTTP
- Long-running processes that rely on WebSocket market data
Who This Is Not For
- One-off scripts where raw HTTP is enough
- Non-Ruby stacks
Start Here (Pick Your Use Case)
Pick the path that matches what you want to build:
- Get live prices fast → Market Feed WebSocket
- Place orders safely → Order Safety
- Build a trading strategy → WebSockets
- Build a trading bot → examples/basic_trading_bot.rb
- Use with Rails → docs/RAILS_INTEGRATION.md
Trust Signals
- CI on supported Rubies — GitHub Actions runs RSpec on Ruby 3.2.0 and 3.3.4, plus RuboCop on every push and pull request
- Typed domain models — Orders, Positions, Holdings, Funds, MarketFeed, OptionChain, Super Orders, and more expose a Ruby-first API instead of raw hashes
- No real API calls in the default test suite — WebMock blocks outbound HTTP and VCR covers cassette-backed integration paths
- Auth lifecycle support — static tokens, dynamic token providers, 401 retry with refresh hooks, and token sanitization in logs
- WebSocket resilience — reconnect, backoff, 429 cool-off, local connection cleanup, and dedicated market/order stream clients
-
Live trading guardrails — order placement is blocked unless
LIVE_TRADING=true, and order attempts emit structured audit logs
Why Not a Thin Wrapper?
Most API clients give you HTTP access. DhanHQ gives you a working Ruby system.
| Instead of | You get |
|---|---|
| JSON parsing and manual field mapping | Typed models |
| Manual auth refresh | Built-in token lifecycle |
| Fragile WebSocket code | Auto-reconnect, backoff, and 429 handling |
| Risky order scripts | Live trading guardrails and audit logs |
Architecture At A Glance
Models own the Ruby API. Resources own HTTP calls. Contracts validate inputs. The transport layer handles auth, retries, rate limiting, and error mapping. WebSockets are a separate subsystem that shares configuration but not the REST stack.
For the full dependency flow and extension pattern, see ARCHITECTURE.md.
✨ 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 - Order audit logging — every order attempt logs machine, IP, environment, and correlation ID as structured JSON
-
Live trading guard — prevents accidental order placement unless
ENV["LIVE_TRADING"]="true" - 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
Reliability & Safety
- retry-on-401 with token refresh
- WebSocket auto-reconnect and backoff
- 429 rate-limit protection
- live trading guard via
LIVE_TRADING=true - structured order audit logs
See ARCHITECTURE.md, docs/TESTING_GUIDE.md, and docs/TROUBLESHOOTING.md for the deeper implementation details.
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.
bundle update / bundle install warnings — If you see "Local specification for rexml-3.2.8 has different dependencies" or "Unresolved or ambiguous specs during Gem::Specification.reset: psych", the bundle still completes successfully. To clear the rexml warning once, run: gem cleanup rexml. The psych message is a known Bundler quirk and can be ignored.
⚠️ 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.
Order Safety
Live Trading Guard
Order placement (create, slicing) is blocked unless you explicitly enable it:
# Production (Render, VPS, etc.)
LIVE_TRADING=true
# Development / Test (default — orders are blocked)
LIVE_TRADING=false # or simply omitAttempting to place an order without LIVE_TRADING=true raises DhanHQ::LiveTradingDisabledError.
Order Audit Logging
Every order attempt (place, modify, slice) automatically logs a structured JSON line at WARN level:
{
"event": "DHAN_ORDER_ATTEMPT",
"hostname": "DESKTOP-SHUBHAM",
"env": "production",
"ipv4": "122.171.22.40",
"ipv6": "2401:4900:894c:8448:1da9:27f1:48e7:61be",
"security_id": "11536",
"correlation_id": "SCALPER_7af1",
"timestamp": "2026-03-17T06:45:22Z"
}This tells you instantly which machine, app, IP, and environment placed the order.
Correlation ID Prefixes
Use per-app prefixes for instant source identification in the Dhan orderbook:
# algo_scalper_api
correlation_id: "SCALPER_#{SecureRandom.hex(4)}"
# algo_trader_api
correlation_id: "TRADER_#{SecureRandom.hex(4)}"The Dhan orderbook will show SCALPER_7af1 or TRADER_3bc9, making the source obvious.
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.
Real-World Examples
These scripts are designed around user goals rather than API surfaces:
| Example | Use case |
|---|---|
| examples/basic_trading_bot.rb | Pull historical data, evaluate a simple signal, and place a guarded order |
| examples/portfolio_monitor.rb | Snapshot funds, holdings, and positions for a monitoring script |
| examples/options_watchlist.rb | Build a live options watchlist with index quotes and option-chain context |
| examples/market_feed_example.rb | Subscribe to major market indices over WebSocket |
| examples/live_order_updates.rb | Track order lifecycle events in real time |
For search-driven discovery and onboarding content, see:
Use Case Guides
- docs/DHAN_API_RUBY_EXAMPLES.md
- docs/DHAN_WEBSOCKET_RUBY_GUIDE.md
- docs/BEST_WAY_TO_USE_DHAN_API_IN_RUBY.md
- docs/DHAN_RUBY_QA.md
📚 Documentation
| Guide | What it covers |
|---|---|
| Architecture | Layering, dependency flow, design patterns, extension points |
| 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, and long-running Ruby processes |
| 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 |
| How To Use Dhan API With Ruby | Search-friendly onboarding guide for Ruby users |
| Build A Trading Bot With Ruby And Dhan | End-to-end tutorial framing for strategy builders |
| Dhan API Ruby Examples | Small answer-style snippets for common Ruby + Dhan tasks |
| Dhan WebSocket Ruby Guide | Query-shaped guide for Dhan market data streaming in Ruby |
| Best Way To Use Dhan API In Ruby | Comparison-focused guide for SDK vs raw HTTP |
| Dhan Ruby Q&A | Publish-ready answers for common Dhan + Ruby questions |
| 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
- Use dynamic token providers in long-running systems instead of hardcoding expiring tokens
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