DhanHQ 2.4.0 → 2.6.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/CHANGELOG.md +103 -7
  4. data/GUIDE.md +57 -39
  5. data/README.md +198 -755
  6. data/docs/API_DOCS_GAPS.md +128 -0
  7. data/docs/API_VERIFICATION.md +10 -11
  8. data/{README1.md → docs/ARCHIVE_README.md} +16 -16
  9. data/docs/AUTHENTICATION.md +72 -10
  10. data/docs/CONFIGURATION.md +109 -0
  11. data/docs/CONSTANTS_REFERENCE.md +477 -0
  12. data/docs/DATA_API_PARAMETERS.md +7 -7
  13. data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
  14. data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
  15. data/docs/SUPER_ORDERS.md +284 -0
  16. data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
  17. data/docs/TESTING_GUIDE.md +84 -82
  18. data/docs/TROUBLESHOOTING.md +117 -0
  19. data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
  20. data/docs/WEBSOCKET_PROTOCOL.md +154 -0
  21. data/lib/DhanHQ/constants.rb +456 -151
  22. data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
  23. data/lib/DhanHQ/contracts/base_contract.rb +22 -0
  24. data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
  25. data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
  26. data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
  27. data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
  28. data/lib/DhanHQ/contracts/order_contract.rb +171 -39
  29. data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
  30. data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
  31. data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
  32. data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
  33. data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
  34. data/lib/DhanHQ/core/base_model.rb +13 -4
  35. data/lib/DhanHQ/helpers/response_helper.rb +2 -2
  36. data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
  37. data/lib/DhanHQ/models/alert_order.rb +29 -11
  38. data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
  39. data/lib/DhanHQ/models/edis.rb +101 -0
  40. data/lib/DhanHQ/models/expired_options_data.rb +6 -12
  41. data/lib/DhanHQ/models/forever_order.rb +8 -5
  42. data/lib/DhanHQ/models/historical_data.rb +0 -8
  43. data/lib/DhanHQ/models/instrument.rb +1 -7
  44. data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
  45. data/lib/DhanHQ/models/kill_switch.rb +23 -11
  46. data/lib/DhanHQ/models/margin.rb +51 -2
  47. data/lib/DhanHQ/models/order.rb +107 -126
  48. data/lib/DhanHQ/models/order_update.rb +7 -13
  49. data/lib/DhanHQ/models/pnl_exit.rb +122 -0
  50. data/lib/DhanHQ/models/position.rb +23 -1
  51. data/lib/DhanHQ/models/postback.rb +114 -0
  52. data/lib/DhanHQ/models/profile.rb +0 -10
  53. data/lib/DhanHQ/models/super_order.rb +13 -3
  54. data/lib/DhanHQ/models/trade.rb +11 -23
  55. data/lib/DhanHQ/resources/ip_setup.rb +16 -5
  56. data/lib/DhanHQ/resources/kill_switch.rb +17 -7
  57. data/lib/DhanHQ/resources/margin_calculator.rb +9 -0
  58. data/lib/DhanHQ/resources/orders.rb +41 -41
  59. data/lib/DhanHQ/resources/pnl_exit.rb +37 -0
  60. data/lib/DhanHQ/resources/positions.rb +8 -0
  61. data/lib/DhanHQ/version.rb +1 -1
  62. data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
  63. data/lib/DhanHQ/ws/orders/client.rb +6 -6
  64. data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
  65. data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
  66. data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
  67. metadata +29 -24
  68. data/TODO-1.md +0 -14
  69. data/TODO.md +0 -127
  70. data/app/services/live/order_update_guard_support.rb +0 -75
  71. data/app/services/live/order_update_hub.rb +0 -76
  72. data/app/services/live/order_update_persistence_support.rb +0 -68
  73. data/docs/PR_2.2.0.md +0 -48
  74. data/examples/comprehensive_websocket_examples.rb +0 -148
  75. data/examples/instrument_finder_test.rb +0 -195
  76. data/examples/live_order_updates.rb +0 -118
  77. data/examples/market_depth_example.rb +0 -144
  78. data/examples/market_feed_example.rb +0 -81
  79. data/examples/order_update_example.rb +0 -105
  80. data/examples/trading_fields_example.rb +0 -215
  81. /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
  82. /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module DhanHQ
8
+ # Enforces the use of `DhanHQ::Constants` instead of hardcoded strings.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # transaction_type: "BUY"
13
+ #
14
+ # # good
15
+ # transaction_type: DhanHQ::Constants::TransactionType::BUY
16
+ class UseConstants < Base
17
+ extend AutoCorrector
18
+
19
+ MSG = "Use `%<constant>s` instead of hardcoded string `%<string>s`."
20
+
21
+ # Unambiguous constants — flagged in any non-key string context.
22
+ CONSTANTS_MAP = {
23
+ "SELL" => "DhanHQ::Constants::TransactionType::SELL",
24
+ "BUY" => "DhanHQ::Constants::TransactionType::BUY",
25
+ "LIMIT" => "DhanHQ::Constants::OrderType::LIMIT",
26
+ "MARKET" => "DhanHQ::Constants::OrderType::MARKET",
27
+ "STOP_LOSS" => "DhanHQ::Constants::OrderType::STOP_LOSS",
28
+ "STOP_LOSS_MARKET" => "DhanHQ::Constants::OrderType::STOP_LOSS_MARKET",
29
+ "IOC" => "DhanHQ::Constants::Validity::IOC",
30
+ "DAY" => "DhanHQ::Constants::Validity::DAY",
31
+ "TRANSIT" => "DhanHQ::Constants::OrderStatus::TRANSIT",
32
+ "PENDING" => "DhanHQ::Constants::OrderStatus::PENDING",
33
+ "CLOSED" => "DhanHQ::Constants::OrderStatus::CLOSED",
34
+ "TRIGGERED" => "DhanHQ::Constants::OrderStatus::TRIGGERED",
35
+ "REJECTED" => "DhanHQ::Constants::OrderStatus::REJECTED",
36
+ "CANCELLED" => "DhanHQ::Constants::OrderStatus::CANCELLED",
37
+ "PART_TRADED" => "DhanHQ::Constants::OrderStatus::PART_TRADED",
38
+ "TRADED" => "DhanHQ::Constants::OrderStatus::TRADED",
39
+ "EXPIRED" => "DhanHQ::Constants::OrderStatus::EXPIRED",
40
+ "MODIFIED" => "DhanHQ::Constants::OrderStatus::MODIFIED",
41
+ "PRE_OPEN" => "DhanHQ::Constants::AmoTime::PRE_OPEN",
42
+ "OPEN_30" => "DhanHQ::Constants::AmoTime::OPEN_30",
43
+ "OPEN_60" => "DhanHQ::Constants::AmoTime::OPEN_60",
44
+ "FUTIDX" => "DhanHQ::Constants::InstrumentType::FUTIDX",
45
+ "OPTIDX" => "DhanHQ::Constants::InstrumentType::OPTIDX",
46
+ "EQUITY" => "DhanHQ::Constants::InstrumentType::EQUITY",
47
+ "FUTSTK" => "DhanHQ::Constants::InstrumentType::FUTSTK",
48
+ "OPTSTK" => "DhanHQ::Constants::InstrumentType::OPTSTK",
49
+ "FUTCOM" => "DhanHQ::Constants::InstrumentType::FUTCOM",
50
+ "OPTFUT" => "DhanHQ::Constants::InstrumentType::OPTFUT",
51
+ "FUTCUR" => "DhanHQ::Constants::InstrumentType::FUTCUR",
52
+ "OPTCUR" => "DhanHQ::Constants::InstrumentType::OPTCUR",
53
+ "CALL" => "DhanHQ::Constants::OptionType::CALL",
54
+ "PUT" => "DhanHQ::Constants::OptionType::PUT",
55
+ "ENTRY_LEG" => "DhanHQ::Constants::LegName::ENTRY_LEG",
56
+ "TARGET_LEG" => "DhanHQ::Constants::LegName::TARGET_LEG",
57
+ "STOP_LOSS_LEG" => "DhanHQ::Constants::LegName::STOP_LOSS_LEG",
58
+ "SINGLE" => "DhanHQ::Constants::OrderFlag::SINGLE",
59
+ "OCO" => "DhanHQ::Constants::OrderFlag::OCO",
60
+ "LONG" => "DhanHQ::Constants::PositionType::LONG",
61
+ "SHORT" => "DhanHQ::Constants::PositionType::SHORT",
62
+ "TECHNICAL_WITH_VALUE" => "DhanHQ::Constants::ComparisonType::TECHNICAL_WITH_VALUE",
63
+ "TECHNICAL_WITH_INDICATOR" => "DhanHQ::Constants::ComparisonType::TECHNICAL_WITH_INDICATOR",
64
+ "TECHNICAL_WITH_CLOSE" => "DhanHQ::Constants::ComparisonType::TECHNICAL_WITH_CLOSE",
65
+ "PRICE_WITH_VALUE" => "DhanHQ::Constants::ComparisonType::PRICE_WITH_VALUE",
66
+ "EMA_200" => "DhanHQ::Constants::IndicatorName::EMA_200",
67
+ "BB_UPPER" => "DhanHQ::Constants::IndicatorName::BB_UPPER",
68
+ "BB_LOWER" => "DhanHQ::Constants::IndicatorName::BB_LOWER",
69
+ "RSI_14" => "DhanHQ::Constants::IndicatorName::RSI_14",
70
+ "ATR_14" => "DhanHQ::Constants::IndicatorName::ATR_14",
71
+ "STOCHASTIC" => "DhanHQ::Constants::IndicatorName::STOCHASTIC",
72
+ "STOCHRSI_14" => "DhanHQ::Constants::IndicatorName::STOCHRSI_14",
73
+ "MACD_26" => "DhanHQ::Constants::IndicatorName::MACD_26",
74
+ "MACD_12" => "DhanHQ::Constants::IndicatorName::MACD_12",
75
+ "MACD_HIST" => "DhanHQ::Constants::IndicatorName::MACD_HIST",
76
+ "SMA_5" => "DhanHQ::Constants::IndicatorName::SMA_5",
77
+ "SMA_10" => "DhanHQ::Constants::IndicatorName::SMA_10",
78
+ "SMA_20" => "DhanHQ::Constants::IndicatorName::SMA_20",
79
+ "SMA_50" => "DhanHQ::Constants::IndicatorName::SMA_50",
80
+ "SMA_100" => "DhanHQ::Constants::IndicatorName::SMA_100",
81
+ "SMA_200" => "DhanHQ::Constants::IndicatorName::SMA_200",
82
+ "EMA_5" => "DhanHQ::Constants::IndicatorName::EMA_5",
83
+ "EMA_10" => "DhanHQ::Constants::IndicatorName::EMA_10",
84
+ "EMA_20" => "DhanHQ::Constants::IndicatorName::EMA_20",
85
+ "EMA_50" => "DhanHQ::Constants::IndicatorName::EMA_50",
86
+ "EMA_100" => "DhanHQ::Constants::IndicatorName::EMA_100",
87
+ "GREATER_THAN_EQUAL" => "DhanHQ::Constants::Operator::GREATER_THAN_EQUAL",
88
+ "LESS_THAN_EQUAL" => "DhanHQ::Constants::Operator::LESS_THAN_EQUAL",
89
+ "EQUAL" => "DhanHQ::Constants::Operator::EQUAL",
90
+ "NOT_EQUAL" => "DhanHQ::Constants::Operator::NOT_EQUAL",
91
+ "CROSSING_UP" => "DhanHQ::Constants::Operator::CROSSING_UP",
92
+ "CROSSING_DOWN" => "DhanHQ::Constants::Operator::CROSSING_DOWN",
93
+ "CROSSING_ANY_SIDE" => "DhanHQ::Constants::Operator::CROSSING_ANY_SIDE",
94
+ "GREATER_THAN" => "DhanHQ::Constants::Operator::GREATER_THAN",
95
+ "LESS_THAN" => "DhanHQ::Constants::Operator::LESS_THAN",
96
+ "ACTIVE" => "DhanHQ::Constants::TriggerStatus::ACTIVE",
97
+ "IDX_I" => "DhanHQ::Constants::ExchangeSegment::IDX_I",
98
+ "NSE_EQ" => "DhanHQ::Constants::ExchangeSegment::NSE_EQ",
99
+ "NSE_FNO" => "DhanHQ::Constants::ExchangeSegment::NSE_FNO",
100
+ "NSE_CURRENCY" => "DhanHQ::Constants::ExchangeSegment::NSE_CURRENCY",
101
+ "NSE_COMM" => "DhanHQ::Constants::ExchangeSegment::NSE_COMM",
102
+ "BSE_EQ" => "DhanHQ::Constants::ExchangeSegment::BSE_EQ",
103
+ "MCX_COMM" => "DhanHQ::Constants::ExchangeSegment::MCX_COMM",
104
+ "BSE_CURRENCY" => "DhanHQ::Constants::ExchangeSegment::BSE_CURRENCY",
105
+ "BSE_FNO" => "DhanHQ::Constants::ExchangeSegment::BSE_FNO",
106
+ "DH-910" => "DhanHQ::Constants::TradingErrorCode::OTHERS",
107
+ "DH-901" => "DhanHQ::Constants::TradingErrorCode::INVALID_AUTHENTICATION",
108
+ "DH-902" => "DhanHQ::Constants::TradingErrorCode::INVALID_ACCESS",
109
+ "DH-903" => "DhanHQ::Constants::TradingErrorCode::USER_ACCOUNT",
110
+ "DH-904" => "DhanHQ::Constants::TradingErrorCode::RATE_LIMIT",
111
+ "DH-905" => "DhanHQ::Constants::TradingErrorCode::INPUT_EXCEPTION",
112
+ "DH-906" => "DhanHQ::Constants::TradingErrorCode::ORDER_ERROR",
113
+ "DH-907" => "DhanHQ::Constants::TradingErrorCode::DATA_ERROR",
114
+ "DH-908" => "DhanHQ::Constants::TradingErrorCode::INTERNAL_SERVER_ERROR",
115
+ "DH-909" => "DhanHQ::Constants::TradingErrorCode::NETWORK_ERROR",
116
+ "CNC" => "DhanHQ::Constants::ProductType::CNC",
117
+ "INTRADAY" => "DhanHQ::Constants::ProductType::INTRADAY",
118
+ "MARGIN" => "DhanHQ::Constants::ProductType::MARGIN",
119
+ "MTF" => "DhanHQ::Constants::ProductType::MTF",
120
+ "CO" => "DhanHQ::Constants::ProductType::CO",
121
+ "BO" => "DhanHQ::Constants::ProductType::BO"
122
+ }.freeze
123
+
124
+ # Ambiguous constants — only flagged when the string is the VALUE side of a hash pair.
125
+ # These strings also appear in natural language contexts (error messages, log output).
126
+ AMBIGUOUS_CONSTANTS = {
127
+ "OPEN" => "DhanHQ::Constants::AmoTime::OPEN",
128
+ "INDEX" => "DhanHQ::Constants::InstrumentType::INDEX"
129
+ }.freeze
130
+
131
+ def on_str(node)
132
+ value = node.value
133
+ return unless value.length >= 2
134
+ return if value.include?(" ")
135
+
136
+ parent = node.parent
137
+ return unless parent
138
+
139
+ # Skip hash keys
140
+ return if parent.pair_type? && parent.key == node
141
+
142
+ # Skip typical ignored methods
143
+ return if parent.send_type? && %i[require require_relative puts print warn raise fail
144
+ class_eval instance_eval].include?(parent.method_name)
145
+
146
+ # Skip %w[] / %i[] array literals
147
+ in_percent_array = parent.array_type? && parent.loc.begin&.source&.start_with?("%w", "%W", "%i", "%I")
148
+ return if in_percent_array
149
+
150
+ # Check unambiguous constants first
151
+ if CONSTANTS_MAP.key?(value)
152
+ constant_path = CONSTANTS_MAP[value]
153
+ add_offense(node, message: format(MSG, constant: constant_path, string: value)) do |corrector|
154
+ corrector.replace(node, constant_path)
155
+ end
156
+ return
157
+ end
158
+
159
+ # Ambiguous constants — only flag when the node is the value side of a hash pair
160
+ return unless AMBIGUOUS_CONSTANTS.key?(value)
161
+ return unless parent.pair_type? && parent.value == node
162
+
163
+ constant_path = AMBIGUOUS_CONSTANTS[value]
164
+ add_offense(node, message: format(MSG, constant: constant_path, string: value)) do |corrector|
165
+ corrector.replace(node, constant_path)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ 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: 2.4.0
4
+ version: 2.6.0
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-02-18 00:00:00.000000000 Z
11
+ date: 2026-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -178,7 +178,9 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
- description: DhanHQ is a simple CLI for DhanHQ API.
181
+ description: A pure-Ruby client wrapping the DhanHQ v2 REST and WebSocket API. Provides
182
+ typed model classes, dry-validation contracts, a token-bucket rate limiter, and
183
+ WebSocket streaming for live market data and order updates.
182
184
  email:
183
185
  - shubhamtaywade82@gmail.com
184
186
  executables:
@@ -196,40 +198,33 @@ files:
196
198
  - GUIDE.md
197
199
  - LICENSE.txt
198
200
  - README.md
199
- - README1.md
200
201
  - RELEASING.md
201
202
  - REVIEW_SUMMARY.md
202
203
  - Rakefile
203
204
  - TAGS
204
- - TODO-1.md
205
- - TODO.md
206
205
  - VERSION_UPDATE.md
207
- - app/services/live/order_update_guard_support.rb
208
- - app/services/live/order_update_hub.rb
209
- - app/services/live/order_update_persistence_support.rb
210
206
  - config/initializers/order_update_hub.rb
211
207
  - core
212
208
  - diagram.html
213
209
  - diagram.md
210
+ - docs/API_DOCS_GAPS.md
214
211
  - docs/API_VERIFICATION.md
212
+ - docs/ARCHIVE_README.md
215
213
  - docs/AUTHENTICATION.md
214
+ - docs/CONFIGURATION.md
215
+ - docs/CONSTANTS_REFERENCE.md
216
216
  - docs/DATA_API_PARAMETERS.md
217
- - docs/PR_2.2.0.md
217
+ - docs/LIVE_ORDER_UPDATES.md
218
+ - docs/RAILS_INTEGRATION.md
219
+ - docs/RAILS_WEBSOCKET_INTEGRATION.md
218
220
  - docs/RELEASE_GUIDE.md
221
+ - docs/STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md
222
+ - docs/SUPER_ORDERS.md
223
+ - docs/TECHNICAL_ANALYSIS.md
219
224
  - docs/TESTING_GUIDE.md
220
- - docs/live_order_updates.md
221
- - docs/rails_integration.md
222
- - docs/rails_websocket_integration.md
223
- - docs/standalone_ruby_websocket_integration.md
224
- - docs/technical_analysis.md
225
- - docs/websocket_integration.md
226
- - examples/comprehensive_websocket_examples.rb
227
- - examples/instrument_finder_test.rb
228
- - examples/live_order_updates.rb
229
- - examples/market_depth_example.rb
230
- - examples/market_feed_example.rb
231
- - examples/order_update_example.rb
232
- - examples/trading_fields_example.rb
225
+ - docs/TROUBLESHOOTING.md
226
+ - docs/WEBSOCKET_INTEGRATION.md
227
+ - docs/WEBSOCKET_PROTOCOL.md
233
228
  - exe/DhanHQ
234
229
  - lib/DhanHQ/auth.rb
235
230
  - lib/DhanHQ/auth/token_generator.rb
@@ -240,19 +235,23 @@ files:
240
235
  - lib/DhanHQ/constants.rb
241
236
  - lib/DhanHQ/contracts/alert_order_contract.rb
242
237
  - lib/DhanHQ/contracts/base_contract.rb
238
+ - lib/DhanHQ/contracts/edis_contract.rb
243
239
  - lib/DhanHQ/contracts/expired_options_data_contract.rb
244
240
  - lib/DhanHQ/contracts/historical_data_contract.rb
245
241
  - lib/DhanHQ/contracts/instrument_list_contract.rb
246
242
  - lib/DhanHQ/contracts/margin_calculator_contract.rb
247
243
  - lib/DhanHQ/contracts/modify_order_contract.rb
244
+ - lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb
248
245
  - lib/DhanHQ/contracts/option_chain_contract.rb
249
246
  - lib/DhanHQ/contracts/order_contract.rb
250
247
  - lib/DhanHQ/contracts/place_order_contract.rb
248
+ - lib/DhanHQ/contracts/pnl_based_exit_contract.rb
251
249
  - lib/DhanHQ/contracts/position_conversion_contract.rb
252
250
  - lib/DhanHQ/contracts/slice_order_contract.rb
253
251
  - lib/DhanHQ/contracts/trade_by_order_id_contract.rb
254
252
  - lib/DhanHQ/contracts/trade_contract.rb
255
253
  - lib/DhanHQ/contracts/trade_history_contract.rb
254
+ - lib/DhanHQ/contracts/user_ip_contract.rb
256
255
  - lib/DhanHQ/core/auth_api.rb
257
256
  - lib/DhanHQ/core/base_api.rb
258
257
  - lib/DhanHQ/core/base_model.rb
@@ -268,6 +267,8 @@ files:
268
267
  - lib/DhanHQ/helpers/validation_helper.rb
269
268
  - lib/DhanHQ/json_loader.rb
270
269
  - lib/DhanHQ/models/alert_order.rb
270
+ - lib/DhanHQ/models/concerns/api_response_handler.rb
271
+ - lib/DhanHQ/models/edis.rb
271
272
  - lib/DhanHQ/models/expired_options_data.rb
272
273
  - lib/DhanHQ/models/forever_order.rb
273
274
  - lib/DhanHQ/models/funds.rb
@@ -282,7 +283,9 @@ files:
282
283
  - lib/DhanHQ/models/option_chain.rb
283
284
  - lib/DhanHQ/models/order.rb
284
285
  - lib/DhanHQ/models/order_update.rb
286
+ - lib/DhanHQ/models/pnl_exit.rb
285
287
  - lib/DhanHQ/models/position.rb
288
+ - lib/DhanHQ/models/postback.rb
286
289
  - lib/DhanHQ/models/profile.rb
287
290
  - lib/DhanHQ/models/super_order.rb
288
291
  - lib/DhanHQ/models/token_response.rb
@@ -305,6 +308,7 @@ files:
305
308
  - lib/DhanHQ/resources/market_feed.rb
306
309
  - lib/DhanHQ/resources/option_chain.rb
307
310
  - lib/DhanHQ/resources/orders.rb
311
+ - lib/DhanHQ/resources/pnl_exit.rb
308
312
  - lib/DhanHQ/resources/positions.rb
309
313
  - lib/DhanHQ/resources/profile.rb
310
314
  - lib/DhanHQ/resources/statements.rb
@@ -346,6 +350,7 @@ files:
346
350
  - lib/dhanhq/analysis/multi_timeframe_analyzer.rb
347
351
  - lib/dhanhq/analysis/options_buying_advisor.rb
348
352
  - lib/dhanhq/contracts/options_buying_advisor_contract.rb
353
+ - lib/rubocop/cop/dhanhq/use_constants.rb
349
354
  - lib/ta.rb
350
355
  - lib/ta/candles.rb
351
356
  - lib/ta/fetcher.rb
@@ -381,5 +386,5 @@ requirements: []
381
386
  rubygems_version: 3.5.11
382
387
  signing_key:
383
388
  specification_version: 4
384
- summary: DhanHQ is a simple CLI for DhanHQ API.
389
+ summary: Ruby client for the DhanHQ v2 REST and WebSocket API (NSE/BSE).
385
390
  test_files: []
data/TODO-1.md DELETED
@@ -1,14 +0,0 @@
1
- # TODO List
2
-
3
- - [x] Wire `DhanHQ::Models::Order.place` to the resource’s `create` endpoint so order placement stops raising `NoMethodError` (`lib/DhanHQ/models/order.rb:73`, `lib/DhanHQ/resources/orders.rb:14`).
4
- - [x] Align `Order#cancel` with `DhanHQ::Resources::Orders#cancel` to restore cancellation support (`lib/DhanHQ/models/order.rb:120`, `lib/DhanHQ/resources/orders.rb:26`).
5
- - [x] Rework `Order#modify` to send a proper payload and capture the response instead of delegating to the broken generic update flow (`lib/DhanHQ/models/order.rb:99`, `lib/DhanHQ/core/base_model.rb:155`).
6
- - [x] Repair or replace the shared CRUD helpers in `BaseModel` so URL construction and response handling behave (`lib/DhanHQ/core/base_model.rb:120`, `lib/DhanHQ/core/base_model.rb:129`, `lib/DhanHQ/core/base_model.rb:155`).
7
- - [x] Make `BaseModel#save!` raise a real exception type (e.g. `DhanHQ::Error`) to avoid the current `TypeError` (`lib/DhanHQ/core/base_model.rb:165`).
8
- - [x] Strip read-only attributes before posting modify requests to pass API validation (`lib/DhanHQ/models/order.rb:166`, `lib/DhanHQ/core/base_model.rb:197`).
9
- - [x] Require `fileutils` in the WebSocket singleton lock so acquiring the lock no longer raises (`lib/DhanHQ/ws/singleton_lock.rb:11`).
10
- - [x] Implement EDIS and kill-switch endpoints surfaced in the OpenAPI spec so the client can call `/edis/bulkform`, `/edis/form`, `/edis/inquire/{isin}`, `/edis/tpin`, and `/killswitch` (`/home/nemesis/dhanhq-bundled.json:827`, `/home/nemesis/dhanhq-bundled.json:873`, `/home/nemesis/dhanhq-bundled.json:949`).
11
- - [x] Correct `ForeverOrders#all` to hit `/v2/forever/orders` instead of the undocumented `/v2/forever/all` path (`lib/DhanHQ/resources/forever_orders.rb:9`, `/home/nemesis/dhanhq-bundled.json:578`).
12
- - [x] Run the existing `MarginCalculatorContract` before posting to `/margincalculator` so required fields like `transactionType` and `productType` are enforced client-side (`lib/DhanHQ/models/margin.rb:23`, `lib/DhanHQ/contracts/margin_calculator_contract.rb:5`).
13
- - [ ] Validate slice-order payloads with `SliceOrderContract` to uphold STOP_LOSS requirements before calling `/orders/slicing` (`lib/DhanHQ/models/order.rb:217`, `lib/DhanHQ/contracts/slice_order_contract.rb:30`, `/home/nemesis/dhanhq-bundled.json:234`).
14
- - [x] Add a contract-backed validation for `Position.convert` so `PositionConversionRequest` fields like `fromProductType` and `convertQty` are checked prior to hitting `/positions/convert` (`lib/DhanHQ/models/position.rb:39`, `/home/nemesis/dhanhq-bundled.json:1391`).
data/TODO.md DELETED
@@ -1,127 +0,0 @@
1
- Below are several suggestions and improvements you can consider to further enhance your gem’s structure, error handling, validations, and overall design:
2
-
3
- ---
4
-
5
- ### **1. Decouple HTTP/Resource Handling from Models**
6
-
7
- - **Inject the API instance:**
8
- Instead of having your BaseModel inherit from or tightly couple to BaseAPI, you already moved to instantiating a shared API object via the `api` method. This is good for testing and future flexibility. Consider allowing dependency injection so that in tests you can supply a mock client.
9
-
10
- - **Separate Resource Objects:**
11
- Create separate “Resource” classes for each endpoint (e.g. Orders, Funds, MarketFeed, etc.) that are solely responsible for forming the correct URL and HTTP call. Then, your models (Order, Funds, etc.) become thin wrappers on top of these resources.
12
-
13
- ---
14
-
15
- ### **2. Improved Error Handling**
16
-
17
- - **Consistent Error Hierarchy:**
18
- You already have a structured set of custom errors. Ensure that all API responses are wrapped in a uniform error response. For instance, for network errors, consider implementing a retry mechanism for transient errors (such as timeouts or 429 responses).
19
-
20
- - **Retry Mechanism:**
21
- Enhance your Client by adding an optional retry strategy (with exponential backoff) for cases when a request fails due to rate limiting or temporary network issues.
22
-
23
- - **Detailed Logging:**
24
- Enable more granular logging (perhaps controlled via configuration) to help troubleshoot errors without exposing sensitive information.
25
-
26
- ---
27
-
28
- ### **3. Enhanced Validations**
29
-
30
- - **Centralize Contracts:**
31
- Use your `BaseContract` as a foundation for all contracts so that common rules and messages are shared. Consider adding custom predicates if you need more complex business rules.
32
- For example, you can write a custom predicate for “valid_order_quantity” that could check against exchange limits.
33
-
34
- - **Error Messages & Localization:**
35
- Consider standardizing error messages across contracts so that errors returned by your gem are predictable. If needed, use a localization mechanism to map raw error keys to user-friendly messages.
36
-
37
- - **Use Dry-Struct (Optional):**
38
- If you want stronger typing and immutability for your models, you might consider using [dry-struct](https://dry-rb.org/gems/dry-struct/) in combination with dry-validation. This can help enforce attribute types and reduce runtime errors.
39
-
40
- ---
41
-
42
- ### **4. Model Improvements and Convenience Methods**
43
-
44
- - **Dynamic Attribute Getters/Setters:**
45
- Your current approach to dynamically assign attribute getters is good. You might consider also generating setters if you want to allow updating the local object state before pushing an update to the API.
46
-
47
- - **CRUD Methods Consistency:**
48
- Ensure that your instance methods like `update`, `delete`, `refresh`, etc., always return either a new instance of the model (with updated values) or a well-formed error object. This will make it easier for users to chain operations.
49
-
50
- - **Merge Updated Attributes Correctly:**
51
- In your `modify` method (for orders, for instance), make sure you correctly merge the existing attributes with the new ones before issuing the PUT request. Currently, there’s a commented line and then an immediate call to `update(attributes)`—this should be updated to use the merged `updated_params`.
52
-
53
- - **Caching or Memoization:**
54
- If your API endpoints don’t change frequently (e.g., for retrieving configuration or instruments), consider caching responses to minimize API calls.
55
-
56
- ---
57
-
58
- ### **5. Testing & VCR**
59
-
60
- - **VCR Cassette Management:**
61
- Ensure that your VCR cassettes capture both successful and error responses. When you need to simulate scenarios (e.g., update order, cancellation), manually edit the cassette files to reflect those states if the real API cannot produce them reliably.
62
-
63
- - **Spec Coverage:**
64
- Write comprehensive specs for each model that exercises both the “happy path” and error cases. For instance, ensure that Order.create returns an order with the proper attributes, and that Order.update merges new attributes as expected.
65
-
66
- ---
67
-
68
- ### **6. Configuration Improvements**
69
-
70
- - **Global Configuration Object:**
71
- Continue iterating on the existing configuration helpers while keeping `DhanHQ.configure_with_env` as the primary entrypoint. Provide any additional toggles by reading from ENV so docs can focus on the single bootstrap path.
72
-
73
- ---
74
-
75
- ### **7. Overall Architecture and Documentation**
76
-
77
- - **Document Model Methods:**
78
- Ensure that each model (Order, Funds, OptionChain, etc.) is well documented. Explain the expected inputs/outputs and any side effects.
79
-
80
- - **Separation of Concerns:**
81
- Keep your gem’s responsibilities clear:
82
-
83
- - **Client:** Low-level HTTP calls with error handling and rate limiting.
84
- - **Resources:** Form URLs and endpoint-specific logic.
85
- - **Models:** Map resource data to business objects and provide CRUD operations with validation.
86
- - **Contracts:** Define dry-validation contracts for input validation.
87
-
88
- - **Extensibility:**
89
- Consider ways to allow users to extend models or override default behavior. For instance, provide hooks (callbacks) before or after an update or create operation.
90
-
91
- ---
92
-
93
- ### **Example of a Revised Modify Method in Order Model**
94
-
95
- Here’s a small snippet that shows how you might update the `modify` method in the Order model to merge attributes properly:
96
-
97
- ```ruby
98
- def modify(new_params)
99
- raise "Order ID is required to modify an order" unless id
100
-
101
- # Merge current attributes with new ones
102
- updated_params = attributes.merge(new_params)
103
- validate_params!(updated_params, DhanHQ::Contracts::ModifyOrderContract)
104
-
105
- # Perform the PUT request with merged parameters
106
- response = self.class.api.put("#{self.class.resource_path}/#{id}", params: updated_params)
107
-
108
- # If the response indicates a transitional status (e.g., "TRANSIT"), re-fetch the order
109
- if success_response?(response) && response[:orderStatus] == "TRANSIT"
110
- return self.class.find(id)
111
- end
112
-
113
- DhanHQ::ErrorObject.new(response)
114
- end
115
- ```
116
-
117
- ---
118
-
119
- ### **Conclusion**
120
-
121
- Implementing these improvements will result in a more robust, testable, and maintainable gem. Enhancing error handling, validations, and separation of concerns not only eases future modifications but also improves the overall developer experience when using the gem.
122
-
123
- Feel free to ask if you’d like more details or examples on any of these suggestions!
124
-
125
- PROGRESS:
126
-
127
- 1. OptionChain working
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Live
4
- # Guard-related helpers for interpreting order update payloads.
5
- module OrderUpdateGuardSupport
6
- module_function
7
-
8
- SEGMENT_MAP = {
9
- %w[NSE E] => "NSE_EQ",
10
- %w[BSE E] => "BSE_EQ",
11
- %w[NSE D] => "NSE_FNO",
12
- %w[BSE D] => "BSE_FNO",
13
- %w[NSE C] => "NSE_CURRENCY",
14
- %w[BSE C] => "BSE_CURRENCY",
15
- %w[MCX M] => "MCX_COMM"
16
- }.freeze
17
-
18
- DEFAULT_SL_PCT = 0.15
19
- DEFAULT_TP_PCT = 0.30
20
- DEFAULT_TRAIL_PCT = 0.01
21
-
22
- def map_segment(exchange, segment)
23
- key = [exchange.to_s.upcase, segment.to_s.upcase]
24
- SEGMENT_MAP.fetch(key, "NSE_EQ")
25
- end
26
-
27
- def position_guard_payload(segment, security_id, order_data)
28
- guard_base_payload(segment, security_id, order_data)
29
- .merge(guard_percentage_payload(segment, security_id))
30
- end
31
-
32
- def guard_base_payload(segment, security_id, order_data)
33
- {
34
- pos_id: nil,
35
- exchange_segment: segment,
36
- security_id: security_id,
37
- entry: average_entry(order_data),
38
- qty: order_data[:TradedQty].to_i,
39
- placed_as: placed_as(order_data),
40
- super_order_id: order_data[:OrderNo].to_s
41
- }
42
- end
43
-
44
- def guard_percentage_payload(segment, security_id)
45
- {
46
- sl_pct: default_sl_pct(segment, security_id),
47
- tp_pct: default_tp_pct(segment, security_id),
48
- trail_pct: default_trail_pct(segment, security_id)
49
- }
50
- end
51
-
52
- def placed_as(order_data)
53
- order_data[:Remarks].to_s.match?(/Super Order/i) ? "super" : "plain"
54
- end
55
-
56
- def average_entry(order_data)
57
- first_price = [order_data[:AvgTradedPrice], order_data[:TradedPrice], order_data[:Price]]
58
- .compact
59
- .first
60
- first_price.to_f
61
- end
62
-
63
- def default_sl_pct(_segment, _security_id)
64
- DEFAULT_SL_PCT
65
- end
66
-
67
- def default_tp_pct(_segment, _security_id)
68
- DEFAULT_TP_PCT
69
- end
70
-
71
- def default_trail_pct(_segment, _security_id)
72
- DEFAULT_TRAIL_PCT
73
- end
74
- end
75
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "singleton"
4
- require_relative "order_update_guard_support"
5
- require_relative "order_update_persistence_support"
6
-
7
- module Live
8
- # OrderUpdateHub listens for order updates over WebSocket and wires them into
9
- # local persistence plus downstream execution helpers.
10
- class OrderUpdateHub
11
- include Singleton
12
-
13
- def start!
14
- return self if @started
15
-
16
- @client = DhanHQ::WS::Orders::Client.new.start
17
- @client.on(:update) { |msg| handle(msg) }
18
- @started = true
19
- self
20
- end
21
-
22
- def stop!
23
- @client&.stop
24
- @started = false
25
- end
26
-
27
- private
28
-
29
- def handle(message)
30
- return unless order_alert?(message)
31
-
32
- order_data = message[:Data] || {}
33
- upsert_local_order(order_data)
34
- handle_entry_leg(order_data)
35
- rescue StandardError => e
36
- Rails.logger.error("[OrderUpdateHub] #{e.class}: #{e.message}")
37
- end
38
-
39
- def order_alert?(message)
40
- message&.dig(:Type) == "order_alert"
41
- end
42
-
43
- def handle_entry_leg(order_data)
44
- return unless entry_leg_traded?(order_data)
45
-
46
- segment = OrderUpdateGuardSupport.map_segment(order_data[:Exchange], order_data[:Segment])
47
- security_id = order_data[:SecurityId].to_s
48
- Live::WsHub.instance.subscribe(seg: segment, sid: security_id) if defined?(Live::WsHub)
49
-
50
- register_position_guard(segment, security_id, order_data)
51
- end
52
-
53
- def register_position_guard(segment, security_id, order_data)
54
- return unless defined?(Execution::PositionGuard)
55
-
56
- payload = OrderUpdateGuardSupport.position_guard_payload(segment, security_id, order_data)
57
- Execution::PositionGuard.instance.register(**payload)
58
- end
59
-
60
- def upsert_local_order(order_data)
61
- order_number = order_data[:OrderNo].to_s
62
- record = BrokerOrder.find_or_initialize_by(order_no: order_number)
63
- attributes = OrderUpdatePersistenceSupport.local_order_attributes(order_data)
64
- record.assign_attributes(attributes)
65
- record.save!
66
- rescue NameError
67
- nil
68
- end
69
-
70
- def entry_leg_traded?(order_data)
71
- order_data[:LegNo].to_i == 1 &&
72
- order_data[:Status].to_s.upcase == "TRADED" &&
73
- order_data[:TradedQty].to_i.positive?
74
- end
75
- end
76
- end