DhanHQ 2.1.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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +26 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/GUIDE.md +555 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +463 -0
  9. data/README1.md +521 -0
  10. data/Rakefile +12 -0
  11. data/TAGS +10 -0
  12. data/TODO-1.md +14 -0
  13. data/TODO.md +127 -0
  14. data/app/services/live/order_update_guard_support.rb +75 -0
  15. data/app/services/live/order_update_hub.rb +76 -0
  16. data/app/services/live/order_update_persistence_support.rb +68 -0
  17. data/config/initializers/order_update_hub.rb +16 -0
  18. data/diagram.html +184 -0
  19. data/diagram.md +34 -0
  20. data/docs/rails_integration.md +304 -0
  21. data/exe/DhanHQ +4 -0
  22. data/lib/DhanHQ/client.rb +116 -0
  23. data/lib/DhanHQ/config.rb +32 -0
  24. data/lib/DhanHQ/configuration.rb +72 -0
  25. data/lib/DhanHQ/constants.rb +170 -0
  26. data/lib/DhanHQ/contracts/base_contract.rb +15 -0
  27. data/lib/DhanHQ/contracts/historical_data_contract.rb +28 -0
  28. data/lib/DhanHQ/contracts/margin_calculator_contract.rb +19 -0
  29. data/lib/DhanHQ/contracts/modify_order_contract copy.rb +100 -0
  30. data/lib/DhanHQ/contracts/modify_order_contract.rb +22 -0
  31. data/lib/DhanHQ/contracts/option_chain_contract.rb +31 -0
  32. data/lib/DhanHQ/contracts/order_contract.rb +102 -0
  33. data/lib/DhanHQ/contracts/place_order_contract.rb +119 -0
  34. data/lib/DhanHQ/contracts/position_conversion_contract.rb +24 -0
  35. data/lib/DhanHQ/contracts/slice_order_contract.rb +111 -0
  36. data/lib/DhanHQ/core/base_api.rb +105 -0
  37. data/lib/DhanHQ/core/base_model.rb +266 -0
  38. data/lib/DhanHQ/core/base_resource.rb +50 -0
  39. data/lib/DhanHQ/core/error_handler.rb +19 -0
  40. data/lib/DhanHQ/error_object.rb +49 -0
  41. data/lib/DhanHQ/errors.rb +45 -0
  42. data/lib/DhanHQ/helpers/api_helper.rb +17 -0
  43. data/lib/DhanHQ/helpers/attribute_helper.rb +72 -0
  44. data/lib/DhanHQ/helpers/model_helper.rb +7 -0
  45. data/lib/DhanHQ/helpers/request_helper.rb +69 -0
  46. data/lib/DhanHQ/helpers/response_helper.rb +98 -0
  47. data/lib/DhanHQ/helpers/validation_helper.rb +36 -0
  48. data/lib/DhanHQ/json_loader.rb +23 -0
  49. data/lib/DhanHQ/models/edis.rb +58 -0
  50. data/lib/DhanHQ/models/forever_order.rb +85 -0
  51. data/lib/DhanHQ/models/funds.rb +50 -0
  52. data/lib/DhanHQ/models/historical_data.rb +77 -0
  53. data/lib/DhanHQ/models/holding.rb +56 -0
  54. data/lib/DhanHQ/models/kill_switch.rb +49 -0
  55. data/lib/DhanHQ/models/ledger_entry.rb +60 -0
  56. data/lib/DhanHQ/models/margin.rb +54 -0
  57. data/lib/DhanHQ/models/market_feed.rb +41 -0
  58. data/lib/DhanHQ/models/option_chain.rb +79 -0
  59. data/lib/DhanHQ/models/order.rb +239 -0
  60. data/lib/DhanHQ/models/position.rb +60 -0
  61. data/lib/DhanHQ/models/profile.rb +44 -0
  62. data/lib/DhanHQ/models/super_order.rb +69 -0
  63. data/lib/DhanHQ/models/trade.rb +79 -0
  64. data/lib/DhanHQ/rate_limiter.rb +107 -0
  65. data/lib/DhanHQ/requests/optionchain/nifty.json +5 -0
  66. data/lib/DhanHQ/requests/optionchain/nifty_expiries.json +4 -0
  67. data/lib/DhanHQ/requests/orders/create.json +0 -0
  68. data/lib/DhanHQ/resources/edis.rb +44 -0
  69. data/lib/DhanHQ/resources/forever_orders.rb +53 -0
  70. data/lib/DhanHQ/resources/funds.rb +21 -0
  71. data/lib/DhanHQ/resources/historical_data.rb +34 -0
  72. data/lib/DhanHQ/resources/holdings.rb +21 -0
  73. data/lib/DhanHQ/resources/kill_switch.rb +21 -0
  74. data/lib/DhanHQ/resources/margin_calculator.rb +22 -0
  75. data/lib/DhanHQ/resources/market_feed.rb +56 -0
  76. data/lib/DhanHQ/resources/option_chain.rb +31 -0
  77. data/lib/DhanHQ/resources/orders.rb +70 -0
  78. data/lib/DhanHQ/resources/positions.rb +29 -0
  79. data/lib/DhanHQ/resources/profile.rb +25 -0
  80. data/lib/DhanHQ/resources/statements.rb +42 -0
  81. data/lib/DhanHQ/resources/super_orders.rb +46 -0
  82. data/lib/DhanHQ/resources/trades.rb +23 -0
  83. data/lib/DhanHQ/version.rb +6 -0
  84. data/lib/DhanHQ/ws/client.rb +182 -0
  85. data/lib/DhanHQ/ws/cmd_bus.rb +38 -0
  86. data/lib/DhanHQ/ws/connection.rb +240 -0
  87. data/lib/DhanHQ/ws/decoder.rb +83 -0
  88. data/lib/DhanHQ/ws/errors.rb +0 -0
  89. data/lib/DhanHQ/ws/orders/client.rb +59 -0
  90. data/lib/DhanHQ/ws/orders/connection.rb +148 -0
  91. data/lib/DhanHQ/ws/orders.rb +13 -0
  92. data/lib/DhanHQ/ws/packets/depth_delta_packet.rb +20 -0
  93. data/lib/DhanHQ/ws/packets/disconnect_packet.rb +15 -0
  94. data/lib/DhanHQ/ws/packets/full_packet.rb +40 -0
  95. data/lib/DhanHQ/ws/packets/header.rb +23 -0
  96. data/lib/DhanHQ/ws/packets/index_packet.rb +14 -0
  97. data/lib/DhanHQ/ws/packets/market_depth_level.rb +21 -0
  98. data/lib/DhanHQ/ws/packets/market_status_packet.rb +14 -0
  99. data/lib/DhanHQ/ws/packets/oi_packet.rb +15 -0
  100. data/lib/DhanHQ/ws/packets/prev_close_packet.rb +16 -0
  101. data/lib/DhanHQ/ws/packets/quote_packet.rb +26 -0
  102. data/lib/DhanHQ/ws/packets/ticker_packet.rb +16 -0
  103. data/lib/DhanHQ/ws/registry.rb +46 -0
  104. data/lib/DhanHQ/ws/segments.rb +75 -0
  105. data/lib/DhanHQ/ws/singleton_lock.rb +54 -0
  106. data/lib/DhanHQ/ws/sub_state.rb +59 -0
  107. data/lib/DhanHQ/ws/websocket_packet_parser.rb +165 -0
  108. data/lib/DhanHQ/ws.rb +37 -0
  109. data/lib/DhanHQ.rb +135 -0
  110. data/lib/ta/technical_analysis.rb +405 -0
  111. data/sig/DhanHQ.rbs +4 -0
  112. data/watchlist.csv +3 -0
  113. metadata +283 -0
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ require_relative "packets/header"
6
+ require_relative "packets/ticker_packet"
7
+ require_relative "packets/quote_packet"
8
+ require_relative "packets/full_packet"
9
+ require_relative "packets/oi_packet"
10
+ require_relative "packets/prev_close_packet"
11
+ require_relative "packets/disconnect_packet"
12
+ require_relative "packets/index_packet"
13
+ require_relative "packets/market_status_packet"
14
+ # optional:
15
+ require_relative "packets/depth_delta_packet"
16
+
17
+ module DhanHQ
18
+ module WS
19
+ # Parses raw WebSocket frames using the binary packet definitions.
20
+ class WebsocketPacketParser
21
+ # Numeric feed codes emitted by the streaming service.
22
+ RESPONSE_CODES = {
23
+ index: 1,
24
+ ticker: 2,
25
+ quote: 4,
26
+ oi: 5,
27
+ prev_close: 6,
28
+ full: 8,
29
+ depth_bid: 41,
30
+ depth_ask: 51,
31
+ disconnect: 50
32
+ }.freeze
33
+
34
+ attr_reader :binary_data, :binary_stream, :header
35
+
36
+ def initialize(binary_data)
37
+ @binary_data = binary_data
38
+ @header = Packets::Header.read(@binary_data) # 8 bytes header
39
+ @binary_stream = StringIO.new(@binary_data.byteslice(8..) || "".b) # payload only
40
+ end
41
+
42
+ # Parses the supplied binary frame and returns a normalized hash.
43
+ #
44
+ # @return [Hash]
45
+ def parse
46
+ body =
47
+ case header.feed_response_code
48
+ when RESPONSE_CODES[:index] then parse_index
49
+ when RESPONSE_CODES[:ticker] then parse_ticker
50
+ when RESPONSE_CODES[:quote] then parse_quote
51
+ when RESPONSE_CODES[:oi] then parse_oi
52
+ when RESPONSE_CODES[:prev_close] then parse_prev_close
53
+ when RESPONSE_CODES[:market_status] then parse_market_status
54
+ when RESPONSE_CODES[:full] then parse_full
55
+ when RESPONSE_CODES[:depth_bid] then parse_depth(:bid)
56
+ when RESPONSE_CODES[:depth_ask] then parse_depth(:ask)
57
+ when RESPONSE_CODES[:disconnect] then parse_disconnect
58
+ else
59
+ DhanHQ.logger&.debug("[WS::Parser] Unknown feed code=#{header.feed_response_code}")
60
+ {}
61
+ end
62
+
63
+ {
64
+ feed_response_code: header.feed_response_code,
65
+ message_length: header.message_length,
66
+ exchange_segment: header.exchange_segment, # numeric (0/1/2/…)
67
+ security_id: header.security_id
68
+ }.merge(body)
69
+ rescue StandardError => e
70
+ DhanHQ.logger.error "[WS::Parser] ❌ #{e.class}: #{e.message}"
71
+ {}
72
+ end
73
+
74
+ private
75
+
76
+ def parse_index
77
+ rec = Packets::IndexPacket.new(@binary_stream.string)
78
+ { index_raw: rec.raw } # keep raw until official layout is known
79
+ end
80
+
81
+ def parse_market_status
82
+ rec = Packets::MarketStatusPacket.new(@binary_stream.string)
83
+ { market_status_raw: rec.raw } # keep raw until official layout is known
84
+ end
85
+
86
+ def parse_ticker
87
+ rec = Packets::TickerPacket.read(@binary_stream.string)
88
+ { ltp: rec.ltp, ltt: rec.ltt }
89
+ end
90
+
91
+ def parse_quote
92
+ rec = Packets::QuotePacket.read(@binary_stream.string)
93
+
94
+ {
95
+ ltp: rec.ltp,
96
+ last_trade_qty: rec.last_trade_qty,
97
+ ltt: rec.ltt,
98
+ atp: rec.atp,
99
+ volume: rec.volume,
100
+ total_sell_qty: rec.total_sell_qty,
101
+ total_buy_qty: rec.total_buy_qty,
102
+ day_open: rec.day_open,
103
+ day_close: rec.day_close,
104
+ day_high: rec.day_high,
105
+ day_low: rec.day_low
106
+ }
107
+ end
108
+
109
+ def parse_full
110
+ rec = Packets::FullPacket.read(@binary_stream.string)
111
+ {
112
+ ltp: rec.ltp,
113
+ last_trade_qty: rec.last_trade_qty,
114
+ ltt: rec.ltt,
115
+ atp: rec.atp,
116
+ volume: rec.volume,
117
+ total_sell_qty: rec.total_sell_qty,
118
+ total_buy_qty: rec.total_buy_qty,
119
+ open_interest: rec.open_interest,
120
+ highest_open_interest: rec.highest_oi,
121
+ lowest_open_interest: rec.lowest_oi,
122
+ day_open: rec.day_open,
123
+ day_close: rec.day_close,
124
+ day_high: rec.day_high,
125
+ day_low: rec.day_low,
126
+ market_depth: rec.market_depth
127
+ }
128
+ end
129
+
130
+ def parse_oi
131
+ rec = Packets::OiPacket.read(@binary_stream.string)
132
+ { open_interest: rec.open_interest }
133
+ end
134
+
135
+ def parse_prev_close
136
+ rec = Packets::PrevClosePacket.read(@binary_stream.string)
137
+ { prev_close: rec.prev_close, oi_prev: rec.oi_prev }
138
+ end
139
+
140
+ # depth_bid / depth_ask incremental
141
+ def parse_depth(side)
142
+ rec = Packets::DepthDeltaPacket.read(@binary_stream.string)
143
+ {
144
+ depth_side: side,
145
+ bid_quantity: rec.bid_quantity,
146
+ ask_quantity: rec.ask_quantity,
147
+ no_of_bid_orders: rec.no_of_bid_orders,
148
+ no_of_ask_orders: rec.no_of_ask_orders,
149
+ bid_price: rec.bid_price,
150
+ ask_price: rec.ask_price
151
+ }
152
+ end
153
+
154
+ def parse_disconnect
155
+ {
156
+ disconnection_code: binary_stream.read(2).unpack1("s>")
157
+ }
158
+ end
159
+
160
+ def debug_log(data)
161
+ DhanHQ.logger.debug { "[WS::Parser] Parsed: #{data.inspect}" }
162
+ end
163
+ end
164
+ end
165
+ end
data/lib/DhanHQ/ws.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ws/client"
4
+ require_relative "ws/orders"
5
+
6
+ module DhanHQ
7
+ # Namespace for the WebSocket streaming client helpers.
8
+ #
9
+ # The helpers provide a simple façade around {DhanHQ::WS::Client} so that
10
+ # applications can start streaming market data with a single method call.
11
+ module WS
12
+ # Establishes a WebSocket connection and yields decoded ticks.
13
+ #
14
+ # @example Subscribe to ticker updates
15
+ # DhanHQ::WS.connect(mode: :ticker) do |tick|
16
+ # puts tick.inspect
17
+ # end
18
+ #
19
+ # @param mode [Symbol] Desired feed mode (:ticker, :quote, :full).
20
+ # @yield [tick]
21
+ # @yieldparam tick [Hash] A decoded tick emitted by the streaming API.
22
+ # @return [DhanHQ::WS::Client] The underlying WebSocket client instance.
23
+ def self.connect(mode: :ticker, &on_tick)
24
+ Client.new(mode: mode).start.on(:tick, &on_tick)
25
+ end
26
+
27
+ # Disconnects every WebSocket client created in the current process.
28
+ #
29
+ # Useful when a long running script needs to ensure all connections are
30
+ # closed (e.g., in signal handlers or +at_exit+ hooks).
31
+ #
32
+ # @return [void]
33
+ def self.disconnect_all_local!
34
+ Registry.stop_all
35
+ end
36
+ end
37
+ end
data/lib/DhanHQ.rb ADDED
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dotenv/load"
4
+ require "logger"
5
+
6
+ # Helper Methods
7
+ require_relative "DhanHQ/helpers/api_helper"
8
+ require_relative "DhanHQ/helpers/attribute_helper"
9
+ require_relative "DhanHQ/helpers/validation_helper"
10
+ require_relative "DhanHQ/helpers/request_helper"
11
+ require_relative "DhanHQ/helpers/response_helper"
12
+ require_relative "DhanHQ/json_loader"
13
+
14
+ require_relative "DhanHQ/core/base_api"
15
+ require_relative "DhanHQ/core/base_resource"
16
+ require_relative "DhanHQ/core/base_model"
17
+ require_relative "DhanHQ/core/error_handler"
18
+
19
+ require_relative "DhanHQ/version"
20
+ require_relative "DhanHQ/errors"
21
+ require_relative "DhanHQ/error_object"
22
+
23
+ require_relative "DhanHQ/client"
24
+ require_relative "DhanHQ/configuration"
25
+ require_relative "DhanHQ/rate_limiter"
26
+
27
+ # Contracts
28
+ require_relative "DhanHQ/contracts/base_contract"
29
+ require_relative "DhanHQ/contracts/historical_data_contract"
30
+ require_relative "DhanHQ/contracts/margin_calculator_contract"
31
+ require_relative "DhanHQ/contracts/position_conversion_contract"
32
+ require_relative "DhanHQ/contracts/slice_order_contract"
33
+
34
+ # Resources
35
+ require_relative "DhanHQ/resources/option_chain"
36
+ require_relative "DhanHQ/resources/orders"
37
+ require_relative "DhanHQ/resources/forever_orders"
38
+ require_relative "DhanHQ/resources/super_orders"
39
+ require_relative "DhanHQ/resources/funds"
40
+ require_relative "DhanHQ/resources/holdings"
41
+ require_relative "DhanHQ/resources/positions"
42
+ require_relative "DhanHQ/resources/statements"
43
+ require_relative "DhanHQ/resources/trades"
44
+ require_relative "DhanHQ/resources/historical_data"
45
+ require_relative "DhanHQ/resources/margin_calculator"
46
+ require_relative "DhanHQ/resources/market_feed"
47
+ require_relative "DhanHQ/resources/edis"
48
+ require_relative "DhanHQ/resources/kill_switch"
49
+ require_relative "DhanHQ/resources/profile"
50
+
51
+ # Models
52
+ require_relative "DhanHQ/models/order"
53
+ require_relative "DhanHQ/models/funds"
54
+ require_relative "DhanHQ/models/option_chain"
55
+ require_relative "DhanHQ/models/forever_order"
56
+ require_relative "DhanHQ/models/super_order"
57
+ require_relative "DhanHQ/models/historical_data"
58
+ require_relative "DhanHQ/models/market_feed"
59
+ require_relative "DhanHQ/models/position"
60
+ require_relative "DhanHQ/models/holding"
61
+ require_relative "DhanHQ/models/ledger_entry"
62
+ require_relative "DhanHQ/models/trade"
63
+ require_relative "DhanHQ/models/margin"
64
+ require_relative "DhanHQ/models/edis"
65
+ require_relative "DhanHQ/models/kill_switch"
66
+ require_relative "DhanHQ/models/profile"
67
+
68
+ require_relative "DhanHQ/constants"
69
+ require_relative "DhanHQ/ws"
70
+ require_relative "DhanHQ/ws/singleton_lock"
71
+
72
+ # The top-level module for the DhanHQ client library.
73
+ #
74
+ # Provides configuration management for setting credentials and API-related settings.
75
+ module DhanHQ
76
+ class Error < StandardError; end
77
+
78
+ class << self
79
+ # Default REST API host used when no custom base URL is provided.
80
+ #
81
+ # @return [String]
82
+ BASE_URL = "https://api.dhan.co/v2"
83
+ # The current configuration instance.
84
+ #
85
+ # @return [DhanHQ::Configuration, nil] The current configuration or `nil` if not set.
86
+ attr_accessor :configuration
87
+
88
+ attr_writer :logger
89
+
90
+ # Configures the DhanHQ client with user-defined settings.
91
+ #
92
+ # @example
93
+ # DhanHQ.configure do |config|
94
+ # config.access_token = 'your_access_token'
95
+ # config.client_id = 'your_client_id'
96
+ # end
97
+ #
98
+ # @yieldparam [DhanHQ::Configuration] configuration The configuration object.
99
+ # @return [void]
100
+ def configure
101
+ self.configuration ||= Configuration.new
102
+ yield(configuration)
103
+ self.logger ||= Logger.new($stdout, level: Logger::INFO)
104
+ end
105
+
106
+ # default logger so calls like DhanHQ.logger&.info never explode
107
+ # Accessor for the logger instance used by the SDK.
108
+ #
109
+ # @return [Logger] The configured logger, defaulting to STDOUT at INFO level.
110
+ def logger
111
+ @logger ||= Logger.new($stdout, level: Logger::INFO)
112
+ end
113
+
114
+ # Configures the DhanHQ client using environment variables.
115
+ #
116
+ # When credentials are injected via `ACCESS_TOKEN` and `CLIENT_ID` this helper
117
+ # can be used to initialise a configuration without a block.
118
+ #
119
+ # @example
120
+ # DhanHQ.configure_with_env
121
+ #
122
+ # @return [void]
123
+ def configure_with_env
124
+ self.configuration ||= Configuration.new
125
+ configuration.access_token = ENV.fetch("ACCESS_TOKEN", nil)
126
+ configuration.client_id = ENV.fetch("CLIENT_ID", nil)
127
+ configuration.base_url = ENV.fetch("DHAN_BASE_URL", BASE_URL)
128
+ configuration.ws_version = ENV.fetch("DHAN_WS_VERSION", configuration.ws_version || 2).to_i
129
+ configuration.ws_order_url = ENV.fetch("DHAN_WS_ORDER_URL", configuration.ws_order_url)
130
+ configuration.ws_user_type = ENV.fetch("DHAN_WS_USER_TYPE", configuration.ws_user_type)
131
+ configuration.partner_id = ENV.fetch("DHAN_PARTNER_ID", configuration.partner_id)
132
+ configuration.partner_secret = ENV.fetch("DHAN_PARTNER_SECRET", configuration.partner_secret)
133
+ end
134
+ end
135
+ end