DhanHQ 3.2.0 → 3.2.1
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/AGENTS.md +23 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/lib/DhanHQ/ai/prompt_helpers.rb +17 -7
- data/lib/DhanHQ/client.rb +53 -34
- data/lib/DhanHQ/models/alert_order.rb +1 -1
- data/lib/DhanHQ/risk/pipeline.rb +0 -2
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/dhan_hq.rb +4 -0
- data/skills/dhanhq-ruby/SKILL.md +208 -0
- data/skills/dhanhq-ruby/examples/fetch_option_chain.rb +54 -0
- data/skills/dhanhq-ruby/examples/gtt_forever_order.rb +65 -0
- data/skills/dhanhq-ruby/examples/historical_data_analysis.rb +89 -0
- data/skills/dhanhq-ruby/examples/iron_condor.rb +137 -0
- data/skills/dhanhq-ruby/examples/live_feed_setup.rb +43 -0
- data/skills/dhanhq-ruby/examples/margin_check.rb +42 -0
- data/skills/dhanhq-ruby/examples/order_management.rb +112 -0
- data/skills/dhanhq-ruby/examples/place_equity_order.rb +36 -0
- data/skills/dhanhq-ruby/examples/place_fno_order.rb +76 -0
- data/skills/dhanhq-ruby/examples/portfolio_summary.rb +74 -0
- data/skills/dhanhq-ruby/examples/super_order_with_sl.rb +57 -0
- data/skills/dhanhq-ruby/references/backtesting-with-dhan.md +65 -0
- data/skills/dhanhq-ruby/references/common-workflows.md +76 -0
- data/skills/dhanhq-ruby/references/error-codes.md +50 -0
- data/skills/dhanhq-ruby/references/funds.md +67 -0
- data/skills/dhanhq-ruby/references/instruments.md +91 -0
- data/skills/dhanhq-ruby/references/live-feed.md +83 -0
- data/skills/dhanhq-ruby/references/market-data.md +119 -0
- data/skills/dhanhq-ruby/references/option-chain.md +71 -0
- data/skills/dhanhq-ruby/references/options-analysis-patterns.md +76 -0
- data/skills/dhanhq-ruby/references/orders.md +203 -0
- data/skills/dhanhq-ruby/references/portfolio.md +93 -0
- data/skills/dhanhq-ruby/references/scanx-data.md +62 -0
- data/skills/dhanhq-ruby/scripts/dhan_helpers.rb +323 -0
- data/skills/dhanhq-ruby/scripts/resolve_security.rb +168 -0
- data/skills/dhanhq-ruby/scripts/trade_logger.rb +131 -0
- data/skills/dhanhq-ruby/scripts/validate_order.rb +169 -0
- metadata +32 -2
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
# rubocop:disable Metrics/AbcSize
|
|
4
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
5
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
6
|
+
# rubocop:disable Metrics/ParameterLists
|
|
7
|
+
# rubocop:disable Style/FormatString
|
|
8
|
+
|
|
9
|
+
require "date"
|
|
10
|
+
|
|
11
|
+
LOT_SIZES = {
|
|
12
|
+
"NIFTY" => 75,
|
|
13
|
+
"BANKNIFTY" => 15,
|
|
14
|
+
"FINNIFTY" => 25,
|
|
15
|
+
"MIDCPNIFTY" => 50,
|
|
16
|
+
"SENSEX" => 10
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
FREEZE_QTY = {
|
|
20
|
+
"NIFTY" => 1800,
|
|
21
|
+
"BANKNIFTY" => 900,
|
|
22
|
+
"FINNIFTY" => 1000,
|
|
23
|
+
"MIDCPNIFTY" => 2800,
|
|
24
|
+
"SENSEX" => 500
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
VALID_EXCHANGE_SEGMENTS = %w[
|
|
28
|
+
NSE_EQ BSE_EQ NSE_FNO BSE_FNO MCX_COMM NSE_CURRENCY BSE_CURRENCY
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
EQUITY_SEGMENTS = %w[NSE_EQ BSE_EQ].freeze
|
|
32
|
+
DERIVATIVE_SEGMENTS = %w[NSE_FNO BSE_FNO MCX_COMM NSE_CURRENCY BSE_CURRENCY].freeze
|
|
33
|
+
|
|
34
|
+
EQUITY_PRODUCT_TYPES = %w[CNC INTRADAY MARGIN MTF].freeze
|
|
35
|
+
DERIVATIVE_PRODUCT_TYPES = %w[INTRADAY MARGIN].freeze
|
|
36
|
+
|
|
37
|
+
VALID_ORDER_TYPES = %w[LIMIT MARKET STOP_LOSS STOP_LOSS_MARKET].freeze
|
|
38
|
+
VALID_TRANSACTION_TYPES = %w[BUY SELL].freeze
|
|
39
|
+
VALID_VALIDITY = %w[DAY IOC].freeze
|
|
40
|
+
|
|
41
|
+
NOTIONAL_WARNING_THRESHOLD = 50_000
|
|
42
|
+
|
|
43
|
+
def _infer_lot_size(trading_symbol)
|
|
44
|
+
return nil if trading_symbol.nil? || trading_symbol.to_s.empty?
|
|
45
|
+
|
|
46
|
+
symbol_upper = trading_symbol.to_s.upcase
|
|
47
|
+
LOT_SIZES.each do |name, lot_size|
|
|
48
|
+
return lot_size if symbol_upper.include?(name)
|
|
49
|
+
end
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def _infer_freeze_qty(trading_symbol)
|
|
54
|
+
return nil if trading_symbol.nil? || trading_symbol.to_s.empty?
|
|
55
|
+
|
|
56
|
+
symbol_upper = trading_symbol.to_s.upcase
|
|
57
|
+
FREEZE_QTY.each do |name, freeze_qty|
|
|
58
|
+
return freeze_qty if symbol_upper.include?(name)
|
|
59
|
+
end
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_order(
|
|
64
|
+
security_id: nil,
|
|
65
|
+
exchange_segment: nil,
|
|
66
|
+
transaction_type: nil,
|
|
67
|
+
quantity: nil,
|
|
68
|
+
order_type: nil,
|
|
69
|
+
product_type: nil,
|
|
70
|
+
price: 0.0,
|
|
71
|
+
trigger_price: 0.0,
|
|
72
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
73
|
+
after_market_order: false,
|
|
74
|
+
trading_symbol: nil,
|
|
75
|
+
lot_size: nil
|
|
76
|
+
)
|
|
77
|
+
errors = []
|
|
78
|
+
warnings = []
|
|
79
|
+
|
|
80
|
+
exchange_segment = exchange_segment&.to_s&.upcase
|
|
81
|
+
transaction_type = transaction_type&.to_s&.upcase
|
|
82
|
+
order_type = order_type&.to_s&.upcase
|
|
83
|
+
product_type = product_type&.to_s&.upcase
|
|
84
|
+
validity = validity&.to_s&.upcase
|
|
85
|
+
|
|
86
|
+
errors << "security_id is required" if security_id.nil? || security_id.to_s.empty?
|
|
87
|
+
errors << "exchange_segment is required" if exchange_segment.nil? || exchange_segment.to_s.empty?
|
|
88
|
+
errors << "transaction_type is required" if transaction_type.nil? || transaction_type.to_s.empty?
|
|
89
|
+
errors << "quantity must be a positive integer" if quantity.nil? || quantity.to_i <= 0
|
|
90
|
+
errors << "order_type is required" if order_type.nil? || order_type.to_s.empty?
|
|
91
|
+
errors << "product_type is required" if product_type.nil? || product_type.to_s.empty?
|
|
92
|
+
|
|
93
|
+
errors << "Invalid exchange_segment: #{exchange_segment}" if exchange_segment && !VALID_EXCHANGE_SEGMENTS.include?(exchange_segment)
|
|
94
|
+
errors << "Invalid transaction_type: #{transaction_type}" if transaction_type && !VALID_TRANSACTION_TYPES.include?(transaction_type)
|
|
95
|
+
errors << "Invalid order_type: #{order_type}" if order_type && !VALID_ORDER_TYPES.include?(order_type)
|
|
96
|
+
errors << "Invalid validity: #{validity}" if validity && !VALID_VALIDITY.include?(validity)
|
|
97
|
+
|
|
98
|
+
errors << "price is required for #{order_type} orders" if %w[LIMIT STOP_LOSS].include?(order_type) && price.to_f <= 0
|
|
99
|
+
errors << "trigger_price is required for #{order_type} orders" if %w[STOP_LOSS STOP_LOSS_MARKET].include?(order_type) && trigger_price.to_f <= 0
|
|
100
|
+
|
|
101
|
+
if exchange_segment && EQUITY_SEGMENTS.include?(exchange_segment) && product_type && !EQUITY_PRODUCT_TYPES.include?(product_type)
|
|
102
|
+
errors << "Invalid product_type '#{product_type}' for equity segment '#{exchange_segment}'. Valid values: #{EQUITY_PRODUCT_TYPES.sort}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if exchange_segment && DERIVATIVE_SEGMENTS.include?(exchange_segment) && product_type && !DERIVATIVE_PRODUCT_TYPES.include?(product_type)
|
|
106
|
+
errors << "Invalid product_type '#{product_type}' for derivative segment '#{exchange_segment}'. Valid values: #{DERIVATIVE_PRODUCT_TYPES.sort}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
warnings << "Dhan's current order docs say API market orders are converted to limit orders with MPP." if order_type == DhanHQ::Constants::OrderType::MARKET
|
|
110
|
+
|
|
111
|
+
effective_lot_size = lot_size || _infer_lot_size(trading_symbol)
|
|
112
|
+
if exchange_segment && DERIVATIVE_SEGMENTS.include?(exchange_segment) && quantity
|
|
113
|
+
if effective_lot_size && quantity.to_i % effective_lot_size != 0
|
|
114
|
+
errors << "Derivative quantity must be a multiple of lot size #{effective_lot_size}. Got #{quantity}."
|
|
115
|
+
elsif effective_lot_size.nil?
|
|
116
|
+
warnings << "Could not resolve a lot size from the provided data. Confirm lot size from the security master before placing."
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
freeze_qty = _infer_freeze_qty(trading_symbol)
|
|
120
|
+
if freeze_qty && quantity.to_i > freeze_qty
|
|
121
|
+
warnings << "Quantity #{quantity} exceeds fallback freeze quantity #{freeze_qty}. Consider place_slice_order() after verifying the latest exchange freeze limits."
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if price.to_f.positive? && quantity
|
|
126
|
+
notional = price.to_f * quantity.to_i
|
|
127
|
+
warnings << "High notional value: Rs. #{"%.2f" % notional} exceeds the Rs. 50,000 warning threshold." if notional > NOTIONAL_WARNING_THRESHOLD
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
unless after_market_order
|
|
131
|
+
now = Time.now
|
|
132
|
+
if now.saturday? || now.sunday?
|
|
133
|
+
warnings << "Market is closed on weekends. Use AMO only if that is intentional."
|
|
134
|
+
elsif now.hour < 9 || (now.hour == 9 && now.min < 15)
|
|
135
|
+
warnings << "Regular market is not yet open."
|
|
136
|
+
elsif now.hour > 15 || (now.hour == 15 && now.min > 30)
|
|
137
|
+
warnings << "Regular market is closed. Use AMO only if that is intentional."
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
{
|
|
142
|
+
"valid" => errors.empty?,
|
|
143
|
+
"errors" => errors,
|
|
144
|
+
"warnings" => warnings
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def print_validation(result)
|
|
149
|
+
if result["valid"]
|
|
150
|
+
puts "Order validation: PASS"
|
|
151
|
+
else
|
|
152
|
+
puts "Order validation: FAIL"
|
|
153
|
+
result["errors"].each { |error| puts " ERROR: #{error}" }
|
|
154
|
+
end
|
|
155
|
+
result["warnings"].each { |warning| puts " WARNING: #{warning}" }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if __FILE__ == $PROGRAM_NAME
|
|
159
|
+
sample = validate_order(
|
|
160
|
+
security_id: "2885",
|
|
161
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
162
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
163
|
+
quantity: 10,
|
|
164
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
165
|
+
product_type: DhanHQ::Constants::ProductType::CNC,
|
|
166
|
+
price: 2450
|
|
167
|
+
)
|
|
168
|
+
print_validation(sample)
|
|
169
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: DhanHQ
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.2.
|
|
4
|
+
version: 3.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shubham Taywade
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -176,8 +176,10 @@ executables:
|
|
|
176
176
|
extensions: []
|
|
177
177
|
extra_rdoc_files: []
|
|
178
178
|
files:
|
|
179
|
+
- AGENTS.md
|
|
179
180
|
- ARCHITECTURE.md
|
|
180
181
|
- CHANGELOG.md
|
|
182
|
+
- CODE_OF_CONDUCT.md
|
|
181
183
|
- GUIDE.md
|
|
182
184
|
- LICENSE.txt
|
|
183
185
|
- README.md
|
|
@@ -435,6 +437,34 @@ files:
|
|
|
435
437
|
- lib/ta/market_calendar.rb
|
|
436
438
|
- lib/ta/technical_analysis.rb
|
|
437
439
|
- sig/DhanHQ.rbs
|
|
440
|
+
- skills/dhanhq-ruby/SKILL.md
|
|
441
|
+
- skills/dhanhq-ruby/examples/fetch_option_chain.rb
|
|
442
|
+
- skills/dhanhq-ruby/examples/gtt_forever_order.rb
|
|
443
|
+
- skills/dhanhq-ruby/examples/historical_data_analysis.rb
|
|
444
|
+
- skills/dhanhq-ruby/examples/iron_condor.rb
|
|
445
|
+
- skills/dhanhq-ruby/examples/live_feed_setup.rb
|
|
446
|
+
- skills/dhanhq-ruby/examples/margin_check.rb
|
|
447
|
+
- skills/dhanhq-ruby/examples/order_management.rb
|
|
448
|
+
- skills/dhanhq-ruby/examples/place_equity_order.rb
|
|
449
|
+
- skills/dhanhq-ruby/examples/place_fno_order.rb
|
|
450
|
+
- skills/dhanhq-ruby/examples/portfolio_summary.rb
|
|
451
|
+
- skills/dhanhq-ruby/examples/super_order_with_sl.rb
|
|
452
|
+
- skills/dhanhq-ruby/references/backtesting-with-dhan.md
|
|
453
|
+
- skills/dhanhq-ruby/references/common-workflows.md
|
|
454
|
+
- skills/dhanhq-ruby/references/error-codes.md
|
|
455
|
+
- skills/dhanhq-ruby/references/funds.md
|
|
456
|
+
- skills/dhanhq-ruby/references/instruments.md
|
|
457
|
+
- skills/dhanhq-ruby/references/live-feed.md
|
|
458
|
+
- skills/dhanhq-ruby/references/market-data.md
|
|
459
|
+
- skills/dhanhq-ruby/references/option-chain.md
|
|
460
|
+
- skills/dhanhq-ruby/references/options-analysis-patterns.md
|
|
461
|
+
- skills/dhanhq-ruby/references/orders.md
|
|
462
|
+
- skills/dhanhq-ruby/references/portfolio.md
|
|
463
|
+
- skills/dhanhq-ruby/references/scanx-data.md
|
|
464
|
+
- skills/dhanhq-ruby/scripts/dhan_helpers.rb
|
|
465
|
+
- skills/dhanhq-ruby/scripts/resolve_security.rb
|
|
466
|
+
- skills/dhanhq-ruby/scripts/trade_logger.rb
|
|
467
|
+
- skills/dhanhq-ruby/scripts/validate_order.rb
|
|
438
468
|
homepage: https://github.com/shubhamtaywade82/dhanhq-client
|
|
439
469
|
licenses:
|
|
440
470
|
- MIT
|