DhanHQ 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +47 -1
- data/CHANGELOG.md +47 -0
- data/README.md +241 -3
- data/docs/AUTHENTICATION.md +3 -5
- data/docs/CONFIGURATION.md +3 -2
- data/docs/CONSTANTS_REFERENCE.md +5 -4
- data/docs/ENDPOINTS_AND_SANDBOX.md +1 -0
- data/docs/LIVE_ORDER_UPDATES.md +5 -10
- data/docs/RAILS_INTEGRATION.md +1 -1
- data/docs/RELEASE_GUIDE.md +13 -2
- data/docs/STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md +2 -2
- data/docs/WEBSOCKET_INTEGRATION.md +13 -20
- data/docs/WEBSOCKET_PROTOCOL.md +7 -3
- data/lib/DhanHQ/agent/key_coercion.rb +36 -0
- data/lib/DhanHQ/agent/tool.rb +37 -0
- data/lib/DhanHQ/agent/tool_catalogue.rb +180 -0
- data/lib/DhanHQ/agent/tool_handlers.rb +116 -0
- data/lib/DhanHQ/agent/tool_registry.rb +17 -261
- data/lib/DhanHQ/agent/tool_schemas.rb +160 -0
- data/lib/DhanHQ/client.rb +58 -2
- data/lib/DhanHQ/concerns/order_audit.rb +35 -4
- data/lib/DhanHQ/configuration.rb +70 -1
- data/lib/DhanHQ/constants.rb +101 -0
- data/lib/DhanHQ/contracts/expired_options_data_contract.rb +1 -9
- data/lib/DhanHQ/contracts/global_stocks_estimator_contract.rb +28 -0
- data/lib/DhanHQ/contracts/global_stocks_modify_order_contract.rb +26 -0
- data/lib/DhanHQ/contracts/global_stocks_order_contract.rb +33 -0
- data/lib/DhanHQ/contracts/global_stocks_place_order_contract.rb +75 -0
- data/lib/DhanHQ/contracts/historical_data_contract.rb +1 -21
- data/lib/DhanHQ/contracts/multi_order_contract.rb +74 -0
- data/lib/DhanHQ/contracts/trade_history_contract.rb +1 -8
- data/lib/DhanHQ/dry_run/ledger.rb +71 -0
- data/lib/DhanHQ/dry_run/simulator.rb +140 -0
- data/lib/DhanHQ/helpers/attribute_helper.rb +23 -0
- data/lib/DhanHQ/models/alert_order.rb +5 -2
- data/lib/DhanHQ/models/global_stocks/funds.rb +56 -0
- data/lib/DhanHQ/models/global_stocks/holding.rb +101 -0
- data/lib/DhanHQ/models/global_stocks/margin.rb +54 -0
- data/lib/DhanHQ/models/global_stocks/market_status.rb +63 -0
- data/lib/DhanHQ/models/global_stocks/order.rb +189 -0
- data/lib/DhanHQ/models/global_stocks/order_estimate.rb +61 -0
- data/lib/DhanHQ/models/global_stocks/trade.rb +74 -0
- data/lib/DhanHQ/models/margin.rb +5 -1
- data/lib/DhanHQ/models/multi_order.rb +130 -0
- data/lib/DhanHQ/resources/global_stocks/funds.rb +25 -0
- data/lib/DhanHQ/resources/global_stocks/holdings.rb +22 -0
- data/lib/DhanHQ/resources/global_stocks/margin_calculator.rb +70 -0
- data/lib/DhanHQ/resources/global_stocks/market_status.rb +22 -0
- data/lib/DhanHQ/resources/global_stocks/orders.rb +112 -0
- data/lib/DhanHQ/resources/global_stocks/trades.rb +31 -0
- data/lib/DhanHQ/resources/multi_orders.rb +58 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/write_paths.rb +57 -0
- data/lib/DhanHQ/ws/client.rb +117 -2
- data/lib/DhanHQ/ws/connection.rb +40 -11
- data/lib/DhanHQ/ws/sub_state.rb +14 -0
- data/lib/dhanhq/analysis/options_buying_advisor.rb +11 -10
- metadata +30 -40
- data/.rspec +0 -3
- data/.rubocop.yml +0 -50
- data/.rubocop_todo.yml +0 -217
- data/AGENTS.md +0 -23
- data/CODE_OF_CONDUCT.md +0 -132
- data/Rakefile +0 -14
- data/TAGS +0 -10
- data/core +0 -0
- data/diagram.html +0 -184
- data/skills/dhanhq-ruby/SKILL.md +0 -207
- data/skills/dhanhq-ruby/examples/fetch_option_chain.rb +0 -54
- data/skills/dhanhq-ruby/examples/gtt_forever_order.rb +0 -65
- data/skills/dhanhq-ruby/examples/historical_data_analysis.rb +0 -89
- data/skills/dhanhq-ruby/examples/iron_condor.rb +0 -137
- data/skills/dhanhq-ruby/examples/live_feed_setup.rb +0 -43
- data/skills/dhanhq-ruby/examples/margin_check.rb +0 -42
- data/skills/dhanhq-ruby/examples/order_management.rb +0 -105
- data/skills/dhanhq-ruby/examples/place_equity_order.rb +0 -36
- data/skills/dhanhq-ruby/examples/place_fno_order.rb +0 -76
- data/skills/dhanhq-ruby/examples/portfolio_summary.rb +0 -74
- data/skills/dhanhq-ruby/examples/super_order_with_sl.rb +0 -57
- data/skills/dhanhq-ruby/references/backtesting-with-dhan.md +0 -65
- data/skills/dhanhq-ruby/references/common-workflows.md +0 -76
- data/skills/dhanhq-ruby/references/error-codes.md +0 -50
- data/skills/dhanhq-ruby/references/funds.md +0 -67
- data/skills/dhanhq-ruby/references/instruments.md +0 -85
- data/skills/dhanhq-ruby/references/live-feed.md +0 -83
- data/skills/dhanhq-ruby/references/market-data.md +0 -119
- data/skills/dhanhq-ruby/references/option-chain.md +0 -71
- data/skills/dhanhq-ruby/references/options-analysis-patterns.md +0 -76
- data/skills/dhanhq-ruby/references/orders.md +0 -201
- data/skills/dhanhq-ruby/references/portfolio.md +0 -93
- data/skills/dhanhq-ruby/references/scanx-data.md +0 -62
- data/skills/dhanhq-ruby/scripts/dhan_helpers.rb +0 -323
- data/skills/dhanhq-ruby/scripts/resolve_security.rb +0 -168
- data/skills/dhanhq-ruby/scripts/trade_logger.rb +0 -131
- data/skills/dhanhq-ruby/scripts/validate_order.rb +0 -169
- data/watchlist.csv +0 -3
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# Live Feed — Complete Reference (Ruby SDK)
|
|
2
|
-
|
|
3
|
-
The Ruby SDK provides three distinct WebSocket interfaces under the `DhanHQ::WS` namespace to handle live data streaming.
|
|
4
|
-
|
|
5
|
-
## 1. Market Feed (`DhanHQ::WS.connect`)
|
|
6
|
-
|
|
7
|
-
Real-time market ticks, last traded prices, quotes, and market depth updates.
|
|
8
|
-
|
|
9
|
-
### Usage
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
# Connect to market feed. Modes: :ticker, :quote, :full
|
|
13
|
-
market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
|
14
|
-
timestamp = tick[:ts] ? Time.at(tick[:ts]) : Time.now
|
|
15
|
-
puts "Tick: #{tick[:segment]}:#{tick[:security_id]} LTP=#{tick[:ltp]} at #{timestamp}"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Subscribe to segments and security IDs
|
|
19
|
-
market_client.subscribe_one(segment: "NSE_EQ", security_id: "2885")
|
|
20
|
-
market_client.subscribe_one(segment: "NSE_EQ", security_id: "1333")
|
|
21
|
-
|
|
22
|
-
# Stop connection
|
|
23
|
-
sleep(15)
|
|
24
|
-
market_client.stop
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Modes
|
|
28
|
-
- `:ticker` - LTP (Last Traded Price) only.
|
|
29
|
-
- `:quote` - OHLC + Volume updates.
|
|
30
|
-
- `:full` - Full quote depth (5 levels) and Open Interest (OI) updates.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 2. Order Updates (`DhanHQ::WS::Orders.connect`)
|
|
35
|
-
|
|
36
|
-
Streams real-time updates for placed, modified, executed, or rejected orders.
|
|
37
|
-
|
|
38
|
-
### Usage
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
orders_client = DhanHQ::WS::Orders.connect do |update|
|
|
42
|
-
puts "Order Update: #{update.order_no} status=#{update.status}"
|
|
43
|
-
puts " Symbol: #{update.symbol}, Traded: #{update.traded_qty}/#{update.quantity}"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Register event callbacks
|
|
47
|
-
orders_client.on(:update) { |order| puts "📝 Order Modified: #{order.order_no}" }
|
|
48
|
-
orders_client.on(:execution) { |exec| puts "✅ Executed: #{exec[:new_traded_qty]} shares" }
|
|
49
|
-
orders_client.on(:order_rejected) { |order| puts "❌ Rejected: #{order.order_no}" }
|
|
50
|
-
|
|
51
|
-
sleep(15)
|
|
52
|
-
orders_client.stop
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 3. Market Depth (`DhanHQ::WS::MarketDepth.connect`)
|
|
58
|
-
|
|
59
|
-
Streams order book depth (bid/ask levels). Supports 20-level depth.
|
|
60
|
-
|
|
61
|
-
### Usage
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
symbols = [
|
|
65
|
-
{ symbol: "RELIANCE", exchange_segment: "NSE_EQ", security_id: "2885" },
|
|
66
|
-
{ symbol: "TCS", exchange_segment: "NSE_EQ", security_id: "11536" }
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) do |depth|
|
|
70
|
-
puts "Symbol: #{depth[:symbol]} Spread: #{depth[:spread]}"
|
|
71
|
-
puts " Best Bid: #{depth[:best_bid]} | Best Ask: #{depth[:best_ask]}"
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
sleep(15)
|
|
75
|
-
depth_client.stop
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Connection Limits & Cleanup
|
|
81
|
-
|
|
82
|
-
- Dhan allows up to **5 concurrent WebSocket connections** per client account.
|
|
83
|
-
- Always call `client.stop` or `DhanHQ::WS.disconnect_all_local!` to prevent socket leaks and rate-limit issues (`429 Too Many Requests`).
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# Market Data — Complete Reference (Ruby SDK)
|
|
2
|
-
|
|
3
|
-
Timestamps returned by the `HistoricalData` model are automatically normalized into Ruby `Time` objects.
|
|
4
|
-
|
|
5
|
-
## Historical Daily Data
|
|
6
|
-
|
|
7
|
-
Use `DhanHQ::Models::HistoricalData.daily(params)`:
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
candles = DhanHQ::Models::HistoricalData.daily(
|
|
11
|
-
security_id: "2885",
|
|
12
|
-
exchange_segment: "NSE_EQ",
|
|
13
|
-
instrument: "EQUITY",
|
|
14
|
-
from_date: "2024-01-01",
|
|
15
|
-
to_date: "2024-12-31",
|
|
16
|
-
expiry_code: 0, # Optional: 0 for current, 1 for next, 2 for far
|
|
17
|
-
oi: false # Optional: true to include open interest
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
first_candle = candles.first
|
|
21
|
-
puts "Date: #{first_candle[:timestamp]}, Close: ₹#{first_candle[:close]}"
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Each candle in the returned array is a Hash containing:
|
|
25
|
-
- `:timestamp` (Ruby `Time` object)
|
|
26
|
-
- `:open` (Float)
|
|
27
|
-
- `:high` (Float)
|
|
28
|
-
- `:low` (Float)
|
|
29
|
-
- `:close` (Float)
|
|
30
|
-
- `:volume` (Integer)
|
|
31
|
-
- `:open_interest` (Float, only if `oi: true` was requested)
|
|
32
|
-
|
|
33
|
-
## Intraday Minute Data
|
|
34
|
-
|
|
35
|
-
Use `DhanHQ::Models::HistoricalData.intraday(params)`:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
candles = DhanHQ::Models::HistoricalData.intraday(
|
|
39
|
-
security_id: "2885",
|
|
40
|
-
exchange_segment: "NSE_EQ",
|
|
41
|
-
instrument: "EQUITY",
|
|
42
|
-
interval: "15", # Supported: "1", "5", "15", "25", "60"
|
|
43
|
-
from_date: "2024-09-11 09:30:00",
|
|
44
|
-
to_date: "2024-09-15 13:00:00",
|
|
45
|
-
oi: false
|
|
46
|
-
)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
- Max 90 days of data can be polled in a single request.
|
|
50
|
-
- Returns a normalized array of candle hashes.
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Market Quote Snapshots
|
|
55
|
-
|
|
56
|
-
REST quote snapshots are accessed via the `DhanHQ::Models::MarketFeed` model.
|
|
57
|
-
|
|
58
|
-
### Ticker Data (LTP only)
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
response = DhanHQ::Models::MarketFeed.ltp(
|
|
62
|
-
"NSE_EQ" => [2885, 1333],
|
|
63
|
-
"NSE_FNO" => [49081]
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
ltp = response[:data]["NSE_EQ"]["2885"][:last_price]
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### OHLC Data
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
response = DhanHQ::Models::MarketFeed.ohlc(
|
|
73
|
-
"NSE_EQ" => [2885]
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
ohlc = response[:data]["NSE_EQ"]["2885"][:ohlc]
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Quote Data (Full Quote Depth & Analytics)
|
|
80
|
-
|
|
81
|
-
```ruby
|
|
82
|
-
response = DhanHQ::Models::MarketFeed.quote(
|
|
83
|
-
"NSE_FNO" => [49081]
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
quote = response[:data]["NSE_FNO"]["49081"]
|
|
87
|
-
puts "LTP: #{quote[:last_price]}, OI: #{quote[:oi]}, Vol: #{quote[:volume]}"
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Expired Options Data
|
|
93
|
-
|
|
94
|
-
Use `DhanHQ::Models::ExpiredOptionsData.fetch(params)` (or direct resource access):
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
response = DhanHQ::Models::ExpiredOptionsData.fetch(
|
|
98
|
-
underlying_scrip: 13,
|
|
99
|
-
exchange_segment: "NSE_FNO",
|
|
100
|
-
expiry_flag: "MONTH",
|
|
101
|
-
expiry_code: 1,
|
|
102
|
-
strike: "ATM",
|
|
103
|
-
option_type: "CALL",
|
|
104
|
-
required_data: ["open", "high", "low", "close", "volume", "oi", "spot"],
|
|
105
|
-
from_date: "2021-08-01",
|
|
106
|
-
to_date: "2021-08-31",
|
|
107
|
-
interval: "1"
|
|
108
|
-
)
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Timestamp Conversion
|
|
114
|
-
|
|
115
|
-
If using raw API responses where timestamps are UNIX epochs, convert them to Ruby Time:
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
time = Time.at(epoch_timestamp)
|
|
119
|
-
```
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Option Chain — Complete Reference (Ruby SDK)
|
|
2
|
-
|
|
3
|
-
For analysis code, use the helper layer `fetch_chain_df` from `scripts/dhan_helpers.rb`.
|
|
4
|
-
|
|
5
|
-
## Expiry List
|
|
6
|
-
|
|
7
|
-
Use `DhanHQ::Models::OptionChain.fetch_expiry_list(params)`:
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(
|
|
11
|
-
underlying_scrip: 13,
|
|
12
|
-
underlying_seg: "IDX_I"
|
|
13
|
-
)
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Option Chain
|
|
17
|
-
|
|
18
|
-
Use `DhanHQ::Models::OptionChain.fetch(params)`:
|
|
19
|
-
|
|
20
|
-
```ruby
|
|
21
|
-
chain = DhanHQ::Models::OptionChain.fetch(
|
|
22
|
-
underlying_scrip: 13,
|
|
23
|
-
underlying_seg: "IDX_I",
|
|
24
|
-
expiry: "2025-03-27"
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
# Underlying LTP
|
|
28
|
-
spot = chain[:last_price]
|
|
29
|
-
|
|
30
|
-
# Strikes sorted array
|
|
31
|
-
chain[:strikes].each do |strike_data|
|
|
32
|
-
puts "Strike: #{strike_data[:strike]}"
|
|
33
|
-
puts "Call LTP: #{strike_data[:call][:last_price]}"
|
|
34
|
-
puts "Put Delta: #{strike_data[:put][:greeks][:delta]}"
|
|
35
|
-
end
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Rate Limits
|
|
39
|
-
- Calls are limited to **1 request every 3 seconds**. The SDK's internal rate limiter handles this.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Normalized Helper Layer
|
|
44
|
-
|
|
45
|
-
```ruby
|
|
46
|
-
require_relative "../scripts/dhan_helpers"
|
|
47
|
-
|
|
48
|
-
chain_rows, spot = fetch_chain_df(
|
|
49
|
-
under_security_id: 13,
|
|
50
|
-
expiry: "2025-03-27",
|
|
51
|
-
under_exchange_segment: "IDX_I"
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
atm = find_atm_row(chain_rows, spot)
|
|
55
|
-
puts "Spot: #{spot}, ATM Strike: #{atm['strike']}, Call LTP: #{atm['ce_ltp']}"
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Normalized columns returned by `fetch_chain_df`:
|
|
59
|
-
- `strike`
|
|
60
|
-
- `ce_security_id`, `pe_security_id`
|
|
61
|
-
- `ce_ltp`, `pe_ltp`
|
|
62
|
-
- `ce_oi`, `pe_oi`
|
|
63
|
-
- `ce_oi_change`, `pe_oi_change`
|
|
64
|
-
- `ce_volume`, `pe_volume`
|
|
65
|
-
- `ce_iv`, `pe_iv`
|
|
66
|
-
- `ce_bid_price`, `pe_bid_price`
|
|
67
|
-
- `ce_ask_price`, `pe_ask_price`
|
|
68
|
-
- `ce_delta`, `pe_delta`
|
|
69
|
-
- `ce_gamma`, `pe_gamma`
|
|
70
|
-
- `ce_theta`, `pe_theta`
|
|
71
|
-
- `ce_vega`, `pe_vega`
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Options Analysis Patterns (Ruby SDK)
|
|
2
|
-
|
|
3
|
-
Use the normalized helper output from `scripts/dhan_helpers.rb` for option chain analysis:
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
require_relative "../scripts/dhan_helpers"
|
|
7
|
-
|
|
8
|
-
chain_rows, spot = fetch_chain_df(
|
|
9
|
-
under_security_id: 13,
|
|
10
|
-
expiry: "2025-03-27",
|
|
11
|
-
under_exchange_segment: "IDX_I"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
atm = find_atm_row(chain_rows, spot)
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Put-Call Ratio (PCR)
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
total_ce_oi = chain_rows.sum { |r| r["ce_oi"].to_f }
|
|
21
|
-
total_pe_oi = chain_rows.sum { |r| r["pe_oi"].to_f }
|
|
22
|
-
pcr = total_ce_oi > 0 ? (total_pe_oi / total_ce_oi) : 0.0
|
|
23
|
-
puts "PCR: #{'%.2f' % pcr}"
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## OI Support / Resistance
|
|
27
|
-
|
|
28
|
-
Find strikes with the highest open interest for resistance (CE) and support (PE):
|
|
29
|
-
|
|
30
|
-
```ruby
|
|
31
|
-
# Top 3 resistance walls (highest Call OI)
|
|
32
|
-
ce_walls = chain_rows.sort_by { |r| -(r["ce_oi"] || 0) }.first(3)
|
|
33
|
-
|
|
34
|
-
# Top 3 support walls (highest Put OI)
|
|
35
|
-
pe_walls = chain_rows.sort_by { |r| -(r["pe_oi"] || 0) }.first(3)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## IV Skew
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
otm_puts = chain_rows.select { |r| r["strike"] < spot }.sort_by { |r| -r["strike"] }.first(3)
|
|
42
|
-
otm_calls = chain_rows.select { |r| r["strike"] > spot }.sort_by { |r| r["strike"] }.first(3)
|
|
43
|
-
|
|
44
|
-
put_iv_avg = otm_puts.sum { |r| r["pe_iv"].to_f } / otm_puts.size.to_f
|
|
45
|
-
call_iv_avg = otm_calls.sum { |r| r["ce_iv"].to_f } / otm_calls.size.to_f
|
|
46
|
-
skew = put_iv_avg - call_iv_avg
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Max Pain
|
|
50
|
-
|
|
51
|
-
Calculate the option strike price where option buyers would experience the maximum loss:
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
def calculate_max_pain(chain_rows)
|
|
55
|
-
strikes = chain_rows.map { |r| r["strike"] }
|
|
56
|
-
pain = {}
|
|
57
|
-
|
|
58
|
-
strikes.each do |test_price|
|
|
59
|
-
total = 0.0
|
|
60
|
-
chain_rows.each do |row|
|
|
61
|
-
strike = row["strike"]
|
|
62
|
-
ce_oi = row["ce_oi"].to_f
|
|
63
|
-
pe_oi = row["pe_oi"].to_f
|
|
64
|
-
|
|
65
|
-
total += [test_price - strike, 0.0].max * ce_oi
|
|
66
|
-
total += [strike - test_price, 0.0].max * pe_oi
|
|
67
|
-
end
|
|
68
|
-
pain[test_price] = total
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
pain.min_by { |_strike, total_pain| total_pain }&.first
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
max_pain_strike = calculate_max_pain(chain_rows)
|
|
75
|
-
puts "Max Pain Strike: #{max_pain_strike}"
|
|
76
|
-
```
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# Orders — Complete Reference (Ruby SDK)
|
|
2
|
-
|
|
3
|
-
Critical API rules:
|
|
4
|
-
- Order placement, modification, cancellation, super orders, and forever orders require static IP whitelisting.
|
|
5
|
-
- Dhan's current order docs say API market orders are converted to limit orders with MPP.
|
|
6
|
-
|
|
7
|
-
## Regular Orders
|
|
8
|
-
|
|
9
|
-
### Place Order
|
|
10
|
-
|
|
11
|
-
In the Ruby SDK, prefer using the model class `DhanHQ::Models::Order.place(params)`:
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
order = DhanHQ::Models::Order.place(
|
|
15
|
-
security_id: "2885",
|
|
16
|
-
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
17
|
-
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
18
|
-
quantity: 10,
|
|
19
|
-
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
20
|
-
product_type: DhanHQ::Constants::ProductType::CNC,
|
|
21
|
-
price: 2450.0,
|
|
22
|
-
validity: DhanHQ::Constants::Validity::DAY,
|
|
23
|
-
correlation_id: "rebalance_001"
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
if order
|
|
27
|
-
puts "Placed Order ID: #{order.order_id}, Status: #{order.order_status}"
|
|
28
|
-
end
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Alternatively, you can use the ActiveRecord-style `new` and `save` flow:
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
order = DhanHQ::Models::Order.new(
|
|
35
|
-
security_id: "2885",
|
|
36
|
-
exchange_segment: "NSE_EQ",
|
|
37
|
-
transaction_type: "BUY",
|
|
38
|
-
quantity: 10,
|
|
39
|
-
order_type: "LIMIT",
|
|
40
|
-
product_type: "CNC",
|
|
41
|
-
price: 2450.0,
|
|
42
|
-
validity: "DAY"
|
|
43
|
-
)
|
|
44
|
-
order.save # Places the order via API
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Slice Order
|
|
48
|
-
|
|
49
|
-
If placing a large quantity that exceeds exchange freeze limits, the SDK handles slicing automatically when using the slice API:
|
|
50
|
-
|
|
51
|
-
```ruby
|
|
52
|
-
order.slice_order(
|
|
53
|
-
slice_quantity: 1000
|
|
54
|
-
)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Modify Order
|
|
58
|
-
|
|
59
|
-
Modify a pending order directly on the model instance:
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
order = DhanHQ::Models::Order.find("112111182198")
|
|
63
|
-
if order.pending?
|
|
64
|
-
order.modify(
|
|
65
|
-
price: 2455.0,
|
|
66
|
-
quantity: 10,
|
|
67
|
-
validity: "DAY"
|
|
68
|
-
)
|
|
69
|
-
end
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
The modify request expects the full placed quantity, not the pending quantity.
|
|
73
|
-
|
|
74
|
-
### Cancel Order
|
|
75
|
-
|
|
76
|
-
Cancel a pending order directly on the model instance:
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
order = DhanHQ::Models::Order.find("112111182198")
|
|
80
|
-
order.cancel # Returns true on success
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Order Retrieval
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
# Fetch all orders for today
|
|
87
|
-
orders = DhanHQ::Models::Order.all
|
|
88
|
-
|
|
89
|
-
# Find order by ID
|
|
90
|
-
order = DhanHQ::Models::Order.find("112111182198")
|
|
91
|
-
|
|
92
|
-
# Find order by correlation ID
|
|
93
|
-
order = DhanHQ::Models::Order.find_by_correlation("rebalance_001")
|
|
94
|
-
|
|
95
|
-
# Fetch today's trades
|
|
96
|
-
trades = DhanHQ::Models::Trade.today
|
|
97
|
-
|
|
98
|
-
# Find trades by order ID
|
|
99
|
-
trade = DhanHQ::Models::Trade.find_by_order_id("112111182198")
|
|
100
|
-
|
|
101
|
-
# Fetch trade history
|
|
102
|
-
history = DhanHQ::Models::Trade.history(
|
|
103
|
-
from_date: "2025-01-01",
|
|
104
|
-
to_date: "2025-01-31",
|
|
105
|
-
page: 0
|
|
106
|
-
)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## Super Orders (Bracket/Cover Orders)
|
|
112
|
-
|
|
113
|
-
### Place Super Order
|
|
114
|
-
|
|
115
|
-
Use the `DhanHQ::Models::SuperOrder.create` method:
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
super_order = DhanHQ::Models::SuperOrder.create(
|
|
119
|
-
security_id: "2885",
|
|
120
|
-
exchange_segment: "NSE_EQ",
|
|
121
|
-
transaction_type: "BUY",
|
|
122
|
-
quantity: 1,
|
|
123
|
-
order_type: "LIMIT",
|
|
124
|
-
product_type: "INTRADAY",
|
|
125
|
-
price: 2450.0,
|
|
126
|
-
target_price: 2500.0,
|
|
127
|
-
stop_loss_price: 2420.0,
|
|
128
|
-
trailing_jump: 10.0
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
puts "Placed Super Order ID: #{super_order.order_id}"
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Modify Super Order
|
|
135
|
-
|
|
136
|
-
```ruby
|
|
137
|
-
super_order.modify(
|
|
138
|
-
leg_name: "ENTRY_LEG",
|
|
139
|
-
price: 2455.0,
|
|
140
|
-
quantity: 1,
|
|
141
|
-
target_price: 2510.0,
|
|
142
|
-
stop_loss_price: 2425.0,
|
|
143
|
-
trailing_jump: 10.0
|
|
144
|
-
)
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
- `ENTRY_LEG` can modify the whole structure while the entry order is `PENDING` or `PART_TRADED`.
|
|
148
|
-
- After entry is `TRADED`, only price and trailing jump of `TARGET_LEG` and `STOP_LOSS_LEG` can be modified.
|
|
149
|
-
|
|
150
|
-
### Cancel Super Order
|
|
151
|
-
|
|
152
|
-
```ruby
|
|
153
|
-
super_order.cancel("ENTRY_LEG") # Cancels all legs
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## Forever Orders (GTT Orders)
|
|
159
|
-
|
|
160
|
-
### Place Forever Order
|
|
161
|
-
|
|
162
|
-
Use the `DhanHQ::Models::ForeverOrder.create` method:
|
|
163
|
-
|
|
164
|
-
```ruby
|
|
165
|
-
# Single Trigger
|
|
166
|
-
gtt_order = DhanHQ::Models::ForeverOrder.create(
|
|
167
|
-
security_id: "2885",
|
|
168
|
-
exchange_segment: "NSE_EQ",
|
|
169
|
-
transaction_type: "BUY",
|
|
170
|
-
product_type: "CNC",
|
|
171
|
-
order_type: "LIMIT",
|
|
172
|
-
quantity: 5,
|
|
173
|
-
price: 2300.0,
|
|
174
|
-
trigger_price: 2305.0,
|
|
175
|
-
order_flag: "SINGLE",
|
|
176
|
-
validity: "DAY"
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
# OCO (One Cancels Other) target + stop loss
|
|
180
|
-
oco_order = DhanHQ::Models::ForeverOrder.create(
|
|
181
|
-
security_id: "2885",
|
|
182
|
-
exchange_segment: "NSE_EQ",
|
|
183
|
-
transaction_type: "SELL",
|
|
184
|
-
product_type: "CNC",
|
|
185
|
-
order_type: "LIMIT",
|
|
186
|
-
quantity: 5,
|
|
187
|
-
price: 2700.00, # Target price (price of first leg)
|
|
188
|
-
trigger_price: 2695.00, # Target trigger price (trigger of first leg)
|
|
189
|
-
price1: 2200.00, # Stop loss price (price of second leg)
|
|
190
|
-
trigger_price1: 2205.00, # Stop loss trigger price (trigger of second leg)
|
|
191
|
-
quantity1: 5, # Stop loss quantity (quantity of second leg)
|
|
192
|
-
order_flag: "OCO",
|
|
193
|
-
validity: "DAY"
|
|
194
|
-
)
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Cancel Forever Order
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
gtt_order.cancel # Returns true on success
|
|
201
|
-
```
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Portfolio And Positions — Complete Reference (Ruby SDK)
|
|
2
|
-
|
|
3
|
-
## Holdings
|
|
4
|
-
|
|
5
|
-
Use `DhanHQ::Models::Holding.all`:
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
holdings = DhanHQ::Models::Holding.all
|
|
9
|
-
|
|
10
|
-
holdings.each do |holding|
|
|
11
|
-
puts "#{holding.trading_symbol} available=#{holding.available_qty}"
|
|
12
|
-
end
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Useful holding fields:
|
|
16
|
-
- `exchange`
|
|
17
|
-
- `trading_symbol`
|
|
18
|
-
- `security_id`
|
|
19
|
-
- `isin`
|
|
20
|
-
- `total_qty`
|
|
21
|
-
- `dp_qty`
|
|
22
|
-
- `t1_qty`
|
|
23
|
-
- `available_qty`
|
|
24
|
-
- `collateral_qty`
|
|
25
|
-
- `avg_cost_price`
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Positions
|
|
30
|
-
|
|
31
|
-
Use `DhanHQ::Models::Position.all`:
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
positions = DhanHQ::Models::Position.all
|
|
35
|
-
open_positions = positions.select { |p| p.net_qty.to_i != 0 }
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Useful position fields:
|
|
39
|
-
- `trading_symbol`
|
|
40
|
-
- `security_id`
|
|
41
|
-
- `position_type` # "LONG" or "SHORT"
|
|
42
|
-
- `exchange_segment`
|
|
43
|
-
- `product_type`
|
|
44
|
-
- `buy_avg`
|
|
45
|
-
- `buy_qty`
|
|
46
|
-
- `sell_avg`
|
|
47
|
-
- `sell_qty`
|
|
48
|
-
- `net_qty`
|
|
49
|
-
- `realized_profit`
|
|
50
|
-
- `unrealized_profit`
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Convert Position
|
|
55
|
-
|
|
56
|
-
Convert an open position (e.g. from Intraday to CNC/Carry Forward):
|
|
57
|
-
|
|
58
|
-
```ruby
|
|
59
|
-
# In the Ruby SDK, call convert directly on a Position model instance
|
|
60
|
-
position = DhanHQ::Models::Position.all.first
|
|
61
|
-
position.convert(
|
|
62
|
-
from_product_type: "INTRADAY",
|
|
63
|
-
to_product_type: "CNC",
|
|
64
|
-
position_type: "LONG",
|
|
65
|
-
convert_qty: 1
|
|
66
|
-
)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## eDIS Authorization
|
|
72
|
-
|
|
73
|
-
For selling delivery holdings, authorization is handled via `DhanHQ::Models::EDIS`:
|
|
74
|
-
|
|
75
|
-
### Step 1: Generate TPIN
|
|
76
|
-
```ruby
|
|
77
|
-
DhanHQ::Models::EDIS.generate_tpin
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Step 2: Open Browser Authorization
|
|
81
|
-
```ruby
|
|
82
|
-
DhanHQ::Models::EDIS.open_browser_for_tpin(
|
|
83
|
-
isin: "INE002A01018",
|
|
84
|
-
qty: 5,
|
|
85
|
-
exchange: "NSE"
|
|
86
|
-
)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Step 3: Inquiry eDIS Approval
|
|
90
|
-
```ruby
|
|
91
|
-
inquiry = DhanHQ::Models::EDIS.inquiry(isin: "INE002A01018")
|
|
92
|
-
puts "Approved Qty: #{inquiry.aprvd_qty}, Status: #{inquiry.status}"
|
|
93
|
-
```
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# ScanX — Fundamental and Technical Data
|
|
2
|
-
|
|
3
|
-
Use ScanX when Dhan APIs do not cover the needed data. Dhan provides execution, quotes, OHLC, option chain, and portfolio. ScanX provides fundamentals, technical indicators, shareholding, and screeners.
|
|
4
|
-
|
|
5
|
-
## Capability Gap
|
|
6
|
-
|
|
7
|
-
| Data needed | Use |
|
|
8
|
-
|------------|-----|
|
|
9
|
-
| PE ratio, EPS, Book Value, PB Ratio | ScanX |
|
|
10
|
-
| Revenue, Net Profit, EBITDA | ScanX |
|
|
11
|
-
| Debt-to-equity, Return on Equity | ScanX |
|
|
12
|
-
| RSI(14), MACD(12,26), ADX(14), ATR(14) | ScanX |
|
|
13
|
-
| Promoter %, FII %, DII %, Public % | ScanX |
|
|
14
|
-
| Quarterly results history (2015–present) | ScanX |
|
|
15
|
-
| Balance Sheet, Cash Flows | ScanX |
|
|
16
|
-
| Stock screeners (fundamental/technical) | ScanX |
|
|
17
|
-
| Live quotes, OHLC, option chain | Dhan |
|
|
18
|
-
| Order execution, portfolio | Dhan |
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Company Page URL Pattern
|
|
23
|
-
|
|
24
|
-
`https://scanx.trade/company/{slug}`
|
|
25
|
-
|
|
26
|
-
Slug rules:
|
|
27
|
-
- Lowercase the full registered company name.
|
|
28
|
-
- Replace spaces with hyphens.
|
|
29
|
-
- Include "ltd" if part of the official name.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Combined Workflow: Analyze on ScanX → Execute on Dhan
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
# Step 1: fetch ScanX page for fundamentals/technicals
|
|
37
|
-
# -> https://scanx.trade/company/reliance-industries-ltd
|
|
38
|
-
# -> Extract metrics: PE, RSI, etc.
|
|
39
|
-
|
|
40
|
-
# Step 2: resolve security_id from Dhan security master
|
|
41
|
-
require_relative "../scripts/dhan_helpers"
|
|
42
|
-
get_client
|
|
43
|
-
|
|
44
|
-
row = resolve_symbol("RELIANCE", "NSE_EQ")
|
|
45
|
-
security_id = row["security_id"]
|
|
46
|
-
|
|
47
|
-
# Step 3: Get live quotes from Dhan
|
|
48
|
-
quote_resp = DhanHQ::Models::MarketFeed.ltp("NSE_EQ" => [security_id.to_i])
|
|
49
|
-
ltp = quote_resp[:data]["NSE_EQ"][security_id.to_s][:last_price].to_f
|
|
50
|
-
|
|
51
|
-
# Step 4: Place order via Dhan
|
|
52
|
-
order = DhanHQ::Models::Order.place(
|
|
53
|
-
security_id: security_id,
|
|
54
|
-
exchange_segment: "NSE_EQ",
|
|
55
|
-
transaction_type: "BUY",
|
|
56
|
-
quantity: 1,
|
|
57
|
-
order_type: "LIMIT",
|
|
58
|
-
product_type: "CNC",
|
|
59
|
-
price: ltp,
|
|
60
|
-
validity: "DAY"
|
|
61
|
-
)
|
|
62
|
-
```
|