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.all
Who 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 DhanHQ
Bleeding 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.
Gem name vs require path
RubyGems normalizes names, so DhanHQ and dhan_hq refer to the same slot — the published name stays DhanHQ and will never change. The require path has used snake_case since v2.1.5:
# Gemfile # Ruby file
gem 'DhanHQ' require 'dhan_hq'
Optional features
The core SDK (require 'dhan_hq') only loads the API client. Technical analysis and the options advisor are opt-in:
require 'dhan_hq/analysis' # DhanHQ::Analysis::OptionsBuyingAdvisor, MultiTimeframeAnalyzer
require 'dhan_hq/ta' # TA::TechnicalAnalysis, TA::Fetcher, TA::Candles
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
end
When 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 omit
Attempting 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.cancel
Positions, Holdings, Funds
DhanHQ::Models::Position.all
DhanHQ::Models::Holding.all
DhanHQ::Models::Fund.balance
Historical Data
= 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})"
end
Market 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") # BANKNIFTY
Market 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]}"
end
Cleanup
DhanHQ::WS.disconnect_all_local! # kills all local WS connections
Super 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
= 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 = .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 alive
Rails 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
Disclaimer
This gem is an independent, community-maintained project and is not officially affiliated with, endorsed by, or supported by Dhan (Mirae Asset Capital Markets). Trading in financial instruments carries significant risk. Use this SDK at your own risk and always verify order placement in a sandbox environment before going live.