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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +23 -0
  3. data/CHANGELOG.md +26 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/lib/DhanHQ/ai/prompt_helpers.rb +17 -7
  6. data/lib/DhanHQ/client.rb +53 -34
  7. data/lib/DhanHQ/models/alert_order.rb +1 -1
  8. data/lib/DhanHQ/risk/pipeline.rb +0 -2
  9. data/lib/DhanHQ/version.rb +1 -1
  10. data/lib/dhan_hq.rb +4 -0
  11. data/skills/dhanhq-ruby/SKILL.md +208 -0
  12. data/skills/dhanhq-ruby/examples/fetch_option_chain.rb +54 -0
  13. data/skills/dhanhq-ruby/examples/gtt_forever_order.rb +65 -0
  14. data/skills/dhanhq-ruby/examples/historical_data_analysis.rb +89 -0
  15. data/skills/dhanhq-ruby/examples/iron_condor.rb +137 -0
  16. data/skills/dhanhq-ruby/examples/live_feed_setup.rb +43 -0
  17. data/skills/dhanhq-ruby/examples/margin_check.rb +42 -0
  18. data/skills/dhanhq-ruby/examples/order_management.rb +112 -0
  19. data/skills/dhanhq-ruby/examples/place_equity_order.rb +36 -0
  20. data/skills/dhanhq-ruby/examples/place_fno_order.rb +76 -0
  21. data/skills/dhanhq-ruby/examples/portfolio_summary.rb +74 -0
  22. data/skills/dhanhq-ruby/examples/super_order_with_sl.rb +57 -0
  23. data/skills/dhanhq-ruby/references/backtesting-with-dhan.md +65 -0
  24. data/skills/dhanhq-ruby/references/common-workflows.md +76 -0
  25. data/skills/dhanhq-ruby/references/error-codes.md +50 -0
  26. data/skills/dhanhq-ruby/references/funds.md +67 -0
  27. data/skills/dhanhq-ruby/references/instruments.md +91 -0
  28. data/skills/dhanhq-ruby/references/live-feed.md +83 -0
  29. data/skills/dhanhq-ruby/references/market-data.md +119 -0
  30. data/skills/dhanhq-ruby/references/option-chain.md +71 -0
  31. data/skills/dhanhq-ruby/references/options-analysis-patterns.md +76 -0
  32. data/skills/dhanhq-ruby/references/orders.md +203 -0
  33. data/skills/dhanhq-ruby/references/portfolio.md +93 -0
  34. data/skills/dhanhq-ruby/references/scanx-data.md +62 -0
  35. data/skills/dhanhq-ruby/scripts/dhan_helpers.rb +323 -0
  36. data/skills/dhanhq-ruby/scripts/resolve_security.rb +168 -0
  37. data/skills/dhanhq-ruby/scripts/trade_logger.rb +131 -0
  38. data/skills/dhanhq-ruby/scripts/validate_order.rb +169 -0
  39. 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.0
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-25 00:00:00.000000000 Z
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