Project

DhanHQ

0.0
A long-lived project that still receives updates
A production-grade Ruby SDK for Dhan API v2 built for algo trading, portfolio monitoring, and live trading systems. Provides typed models, token lifecycle management, dry-validation contracts, resilient WebSocket streaming, and safety-focused order workflows for Ruby applications.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Project Readme

DhanHQ — The Ruby SDK for Dhan API v2

Gem Version CI Ruby License: MIT

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:


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

DhanHQ architecture overview

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 modelsfind, all, where, save, cancel across 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_chain directly 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.parse and 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.

⚠️ 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
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

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})"
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
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 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


📚 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: :quote for most strategies; :full only 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

License

MIT