DhanHQ 2.1.0 → 2.1.5

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.rubocop_todo.yml +185 -0
  4. data/CHANGELOG.md +24 -0
  5. data/GUIDE.md +44 -44
  6. data/README.md +40 -14
  7. data/docs/rails_integration.md +1 -1
  8. data/docs/technical_analysis.md +144 -0
  9. data/lib/DhanHQ/config.rb +1 -0
  10. data/lib/DhanHQ/constants.rb +4 -6
  11. data/lib/DhanHQ/contracts/instrument_list_contract.rb +12 -0
  12. data/lib/DhanHQ/contracts/modify_order_contract.rb +1 -0
  13. data/lib/DhanHQ/contracts/option_chain_contract.rb +11 -1
  14. data/lib/DhanHQ/helpers/request_helper.rb +5 -1
  15. data/lib/DhanHQ/models/instrument.rb +56 -0
  16. data/lib/DhanHQ/models/option_chain.rb +2 -0
  17. data/lib/DhanHQ/rate_limiter.rb +4 -2
  18. data/lib/DhanHQ/resources/instruments.rb +28 -0
  19. data/lib/DhanHQ/version.rb +1 -1
  20. data/lib/DhanHQ/ws/client.rb +1 -1
  21. data/lib/DhanHQ/ws/connection.rb +1 -1
  22. data/lib/DhanHQ/ws/orders/client.rb +3 -0
  23. data/lib/DhanHQ/ws/orders/connection.rb +5 -6
  24. data/lib/DhanHQ/ws/orders.rb +3 -2
  25. data/lib/DhanHQ/ws/registry.rb +1 -0
  26. data/lib/DhanHQ/ws/segments.rb +4 -4
  27. data/lib/DhanHQ/ws/sub_state.rb +1 -1
  28. data/lib/{DhanHQ.rb → dhan_hq.rb} +8 -0
  29. data/lib/dhanhq/analysis/helpers/bias_aggregator.rb +83 -0
  30. data/lib/dhanhq/analysis/helpers/moneyness_helper.rb +24 -0
  31. data/lib/dhanhq/analysis/multi_timeframe_analyzer.rb +232 -0
  32. data/lib/dhanhq/analysis/options_buying_advisor.rb +251 -0
  33. data/lib/dhanhq/contracts/options_buying_advisor_contract.rb +24 -0
  34. data/lib/ta/candles.rb +52 -0
  35. data/lib/ta/fetcher.rb +70 -0
  36. data/lib/ta/indicators.rb +169 -0
  37. data/lib/ta/market_calendar.rb +51 -0
  38. data/lib/ta/technical_analysis.rb +94 -303
  39. data/lib/ta.rb +7 -0
  40. metadata +18 -4
  41. data/lib/DhanHQ/ws/errors.rb +0 -0
  42. /data/lib/DhanHQ/contracts/{modify_order_contract copy.rb → modify_order_contract_copy.rb} +0 -0
@@ -16,53 +16,14 @@ rescue LoadError => e
16
16
  warn "technical-analysis not available: #{e.message}"
17
17
  end
18
18
 
19
- require "DhanHQ"
20
-
21
- unless defined?(MarketCalendar)
22
- module MarketCalendar
23
- MARKET_HOLIDAYS = [
24
- Date.new(2025, 8, 15),
25
- Date.new(2025, 10, 2),
26
- Date.new(2025, 8, 27)
27
- ].freeze
28
-
29
- def self.weekday?(date)
30
- w = date.wday
31
- w >= 1 && w <= 5
32
- end
33
-
34
- def self.trading_day?(date)
35
- weekday?(date) && !MARKET_HOLIDAYS.include?(date)
36
- end
37
-
38
- def self.last_trading_day(from: Date.today)
39
- d = from
40
- d -= 1 until trading_day?(d)
41
- d
42
- end
43
-
44
- def self.today_or_last_trading_day
45
- trading_day?(Date.today) ? Date.today : last_trading_day(from: Date.today)
46
- end
47
-
48
- # Returns the trading day N days back from the given trading day.
49
- # Example: trading_days_ago(2025-10-07, 0) -> 2025-10-07 (if trading day)
50
- # trading_days_ago(2025-10-07, 1) -> previous trading day
51
- def self.trading_days_ago(date, n)
52
- raise ArgumentError, "n must be >= 0" if n.to_i.negative?
53
-
54
- d = trading_day?(date) ? date : today_or_last_trading_day
55
- count = 0
56
- while count < n
57
- d = last_trading_day(from: d)
58
- count += 1
59
- end
60
- d
61
- end
62
- end
63
- end
19
+ require "dhan_hq"
20
+ require_relative "market_calendar"
21
+ require_relative "candles"
22
+ require_relative "indicators"
23
+ require_relative "fetcher"
64
24
 
65
25
  module TA
26
+ # Main technical analysis orchestrator for multi-timeframe indicator computation
66
27
  class TechnicalAnalysis
67
28
  DEFAULTS = {
68
29
  rsi_period: 14,
@@ -70,24 +31,27 @@ module TA
70
31
  adx_period: 14,
71
32
  macd_fast: 12,
72
33
  macd_slow: 26,
73
- macd_signal: 9
34
+ macd_signal: 9,
35
+ throttle_seconds: 1.0,
36
+ max_retries: 3
74
37
  }.freeze
75
38
 
76
39
  def initialize(options = {})
77
40
  @opts = DEFAULTS.merge(options.transform_keys(&:to_sym))
41
+ @fetcher = Fetcher.new(throttle_seconds: @opts[:throttle_seconds], max_retries: @opts[:max_retries])
78
42
  end
79
43
 
80
44
  def compute(exchange_segment:, instrument:, security_id:, from_date: nil, to_date: nil, days_back: nil,
81
45
  intervals: [1, 5, 15, 25, 60])
82
- if to_date.nil? || to_date.to_s.strip.empty?
83
- to_date = MarketCalendar.today_or_last_trading_day.strftime("%Y-%m-%d")
84
- end
85
- if (from_date.nil? || from_date.to_s.strip.empty?) && days_back && days_back.to_i > 0
86
- to_d = Date.parse(to_date)
87
- n_back = [days_back.to_i - 1, 0].max
88
- from_date = MarketCalendar.trading_days_ago(to_d, n_back).strftime("%Y-%m-%d")
89
- end
90
- from_date ||= to_date
46
+ # Normalize to_date: default to last trading day; if provided and non-trading, roll back
47
+ to_date = normalize_to_date(to_date)
48
+
49
+ # Auto-calculate required trading days if not provided
50
+ days_back = auto_days_needed(intervals) if days_back.nil? || days_back.to_i <= 0
51
+
52
+ # Derive/normalize from_date
53
+ from_date = normalize_from_date(from_date, to_date, days_back)
54
+
91
55
  base_params = {
92
56
  exchange_segment: exchange_segment,
93
57
  instrument: instrument,
@@ -96,17 +60,15 @@ module TA
96
60
  to_date: to_date
97
61
  }
98
62
 
99
- one_min_candles = candles(fetch_intraday_windowed(base_params, 1))
100
-
101
63
  frames = {}
64
+ interval_key = { 1 => :m1, 5 => :m5, 15 => :m15, 25 => :m25, 60 => :m60 }
102
65
  intervals.each do |ivl|
103
- case ivl.to_i
104
- when 1 then frames[:m1] = one_min_candles
105
- when 5 then frames[:m5] = resample(one_min_candles, 5)
106
- when 15 then frames[:m15] = resample(one_min_candles, 15)
107
- when 25 then frames[:m25] = resample(one_min_candles, 25)
108
- when 60 then frames[:m60] = resample(one_min_candles, 60)
109
- end
66
+ key = interval_key[ivl.to_i]
67
+ next unless key
68
+
69
+ raw = @fetcher.intraday_windowed(base_params, ivl.to_i)
70
+ frames[key] = Candles.from_series(raw)
71
+ sleep_with_jitter # throttle between intervals
110
72
  end
111
73
 
112
74
  {
@@ -123,15 +85,15 @@ module TA
123
85
 
124
86
  def compute_from_file(path:, base_interval: 1, intervals: [1, 5, 15, 25, 60])
125
87
  raw = JSON.parse(File.read(path))
126
- base = candles(raw)
88
+ base = Candles.from_series(raw)
127
89
  frames = {}
128
90
  intervals.each do |ivl|
129
91
  case ivl.to_i
130
- when 1 then frames[:m1] = (base_interval == 1 ? base : resample(base, 1))
131
- when 5 then frames[:m5] = resample(base, 5)
132
- when 15 then frames[:m15] = resample(base, 15)
133
- when 25 then frames[:m25] = resample(base, 25)
134
- when 60 then frames[:m60] = resample(base, 60)
92
+ when 1 then frames[:m1] = (base_interval == 1 ? base : Candles.resample(base, 1))
93
+ when 5 then frames[:m5] = Candles.resample(base, 5)
94
+ when 15 then frames[:m15] = Candles.resample(base, 15)
95
+ when 25 then frames[:m25] = Candles.resample(base, 25)
96
+ when 60 then frames[:m60] = Candles.resample(base, 60)
135
97
  end
136
98
  end
137
99
  { indicators: frames.transform_values { |candles| compute_for(candles) } }
@@ -139,95 +101,87 @@ module TA
139
101
 
140
102
  private
141
103
 
142
- def fetch_intraday(params, interval)
143
- DhanHQ::Models::HistoricalData.intraday(
144
- security_id: params[:security_id],
145
- exchange_segment: params[:exchange_segment],
146
- instrument: params[:instrument],
147
- interval: interval.to_s,
148
- from_date: params[:from_date],
149
- to_date: params[:to_date]
150
- )
104
+ def sleep_with_jitter(multiplier = 1.0)
105
+ base = (@opts[:throttle_seconds] || 3.0).to_f * multiplier
106
+ jitter = rand * 0.3
107
+ sleep(base + jitter)
151
108
  end
152
109
 
153
- def fetch_intraday_windowed(params, interval)
154
- from_d = Date.parse(params[:from_date])
155
- to_d = Date.parse(params[:to_date])
156
- max_span = 90
157
- return fetch_intraday(params, interval) if (to_d - from_d).to_i <= max_span
110
+ def normalize_to_date(to_date)
111
+ return MarketCalendar.today_or_last_trading_day.strftime("%Y-%m-%d") if to_date.nil? || to_date.to_s.strip.empty?
158
112
 
159
- merged = { "open" => [], "high" => [], "low" => [], "close" => [], "volume" => [], "timestamp" => [] }
160
- cursor = from_d
161
- while cursor <= to_d
162
- chunk_to = [cursor + max_span, to_d].min
163
- chunk_params = params.merge(from_date: cursor.strftime("%Y-%m-%d"), to_date: chunk_to.strftime("%Y-%m-%d"))
164
- part = fetch_intraday(chunk_params, interval)
165
- %w[open high low close volume timestamp].each do |k|
166
- ary = (part[k] || part[k.to_sym]) || []
167
- merged[k].concat(Array(ary))
168
- end
169
- cursor = chunk_to + 1
113
+ to_d_raw = begin
114
+ Date.parse(to_date)
115
+ rescue StandardError
116
+ nil
117
+ end
118
+ if to_d_raw && !MarketCalendar.trading_day?(to_d_raw)
119
+ MarketCalendar.last_trading_day(from: to_d_raw).strftime("%Y-%m-%d")
120
+ else
121
+ to_date
170
122
  end
171
- merged
172
123
  end
173
124
 
174
- def parse_time_like(val)
175
- return Time.at(val) if val.is_a?(Numeric)
176
-
177
- s = val.to_s
178
- return Time.at(s.to_i) if /\A\d+\z/.match?(s) && s.length >= 10 && s.length <= 13
125
+ def normalize_from_date(from_date, to_date, days_back)
126
+ if (from_date.nil? || from_date.to_s.strip.empty?) && days_back&.to_i&.positive?
127
+ to_d = Date.parse(to_date)
128
+ n_back = [days_back.to_i - 1, 0].max
129
+ return MarketCalendar.trading_days_ago(to_d, n_back).strftime("%Y-%m-%d")
130
+ end
131
+ if from_date && !from_date.to_s.strip.empty?
132
+ f_d_raw = begin
133
+ Date.parse(from_date)
134
+ rescue StandardError
135
+ nil
136
+ end
137
+ if f_d_raw && !MarketCalendar.trading_day?(f_d_raw)
138
+ fd = f_d_raw
139
+ fd += 1 until MarketCalendar.trading_day?(fd)
140
+ to_d = Date.parse(to_date)
141
+ return [fd, to_d].min.strftime("%Y-%m-%d")
142
+ end
143
+ return from_date
144
+ end
145
+ to_date
146
+ end
179
147
 
180
- Time.parse(s)
148
+ # Calculate how many bars we need based on indicator periods
149
+ def required_bars_for_indicators
150
+ rsi_need = (@opts[:rsi_period] || 14).to_i + 1
151
+ atr_need = (@opts[:atr_period] || 14).to_i + 1
152
+ adx_need = (@opts[:adx_period] || 14).to_i * 2
153
+ macd_need = (@opts[:macd_slow] || 26).to_i
154
+ [rsi_need, atr_need, adx_need, macd_need].max
181
155
  end
182
156
 
183
- def candles(series)
184
- ts = series["timestamp"] || series[:timestamp]
185
- open = series["open"] || series[:open]
186
- high = series["high"] || series[:high]
187
- low = series["low"] || series[:low]
188
- close = series["close"] || series[:close]
189
- vol = series["volume"] || series[:volume]
190
- return [] unless ts && open && high && low && close && vol
191
- return [] if close.empty?
157
+ def bars_per_trading_day(interval_minutes)
158
+ minutes = interval_minutes.to_i
159
+ return 1 if minutes <= 0
192
160
 
193
- (0...close.size).map do |i|
194
- { t: parse_time_like(ts[i]), o: open[i].to_f, h: high[i].to_f, l: low[i].to_f, c: close[i].to_f,
195
- v: vol[i].to_f }
196
- end
197
- rescue StandardError
198
- []
161
+ (375.0 / minutes).floor
199
162
  end
200
163
 
201
- def resample(candles, minutes)
202
- return candles if minutes == 1
203
-
204
- grouped = {}
205
- candles.each do |c|
206
- key = Time.at((c[:t].to_i / 60) / minutes * minutes * 60)
207
- b = (grouped[key] ||= { t: key, o: c[:o], h: c[:h], l: c[:l], c: c[:c], v: 0.0 })
208
- b[:h] = [b[:h], c[:h]].max
209
- b[:l] = [b[:l], c[:l]].min
210
- b[:c] = c[:c]
211
- b[:v] += c[:v]
212
- end
213
- grouped.keys.sort.map { |k| grouped[k] }
164
+ def days_needed_for_interval(interval_minutes)
165
+ need = required_bars_for_indicators
166
+ per_day = [bars_per_trading_day(interval_minutes), 1].max
167
+ ((need + per_day - 1) / per_day)
214
168
  end
215
169
 
216
- def closes(candles) = candles.map { |c| c[:c] }
217
- def highs(candles) = candles.map { |c| c[:h] }
218
- def lows(candles) = candles.map { |c| c[:l] }
170
+ def auto_days_needed(intervals)
171
+ Array(intervals).map { |ivl| days_needed_for_interval(ivl.to_i) }.max || 1
172
+ end
219
173
 
220
174
  def compute_for(candles)
221
- c = closes(candles)
222
- h = highs(candles)
223
- l = lows(candles)
175
+ c = candles.map { |k| k[:c] }
176
+ h = candles.map { |k| k[:h] }
177
+ l = candles.map { |k| k[:l] }
224
178
  return { rsi: nil, macd: { macd: nil, signal: nil, hist: nil }, adx: nil, atr: nil } if c.empty?
225
179
 
226
180
  {
227
- rsi: safe_last(rsi(c, @opts[:rsi_period])),
228
- macd: macd(c, @opts[:macd_fast], @opts[:macd_slow], @opts[:macd_signal]),
229
- adx: safe_last(adx(h, l, c, @opts[:adx_period])),
230
- atr: safe_last(atr(h, l, c, @opts[:atr_period]))
181
+ rsi: safe_last(Indicators.rsi(c, @opts[:rsi_period])),
182
+ macd: Indicators.macd(c, @opts[:macd_fast], @opts[:macd_slow], @opts[:macd_signal]),
183
+ adx: safe_last(Indicators.adx(h, l, c, @opts[:adx_period])),
184
+ atr: safe_last(Indicators.atr(h, l, c, @opts[:atr_period]))
231
185
  }
232
186
  end
233
187
 
@@ -238,168 +192,5 @@ module TA
238
192
  rescue StandardError
239
193
  nil
240
194
  end
241
-
242
- # ---- Indicator adapters (mirror bin script) ----
243
- def rsi(series, period)
244
- if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:RSI)
245
- return RubyTechnicalAnalysis::RSI.new(series: series, period: period).call
246
- end
247
- if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:rsi)
248
- return TechnicalAnalysis.rsi(series, period: period)
249
- end
250
-
251
- simple_rsi(series, period)
252
- end
253
-
254
- def macd(series, fast, slow, signal)
255
- if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:MACD)
256
- out = RubyTechnicalAnalysis::MACD.new(series: series, fast_period: fast, slow_period: slow,
257
- signal_period: signal).call
258
- if out.is_a?(Hash)
259
- m = out[:macd]
260
- s = out[:signal]
261
- h = out[:histogram] || out[:hist]
262
- m = m.last if m.is_a?(Array)
263
- s = s.last if s.is_a?(Array)
264
- h = h.last if h.is_a?(Array)
265
- return { macd: m, signal: s, hist: h }
266
- end
267
- end
268
- if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:macd)
269
- out = TechnicalAnalysis.macd(series, fast: fast, slow: slow, signal: signal)
270
- if out.is_a?(Hash)
271
- m = out[:macd]
272
- s = out[:signal]
273
- h = out[:hist]
274
- m = m.last if m.is_a?(Array)
275
- s = s.last if s.is_a?(Array)
276
- h = h.last if h.is_a?(Array)
277
- return { macd: m, signal: s, hist: h }
278
- end
279
- end
280
- simple_macd(series, fast, slow, signal)
281
- end
282
-
283
- def adx(high, low, close, period)
284
- if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:ADX)
285
- return RubyTechnicalAnalysis::ADX.new(high: high, low: low, close: close, period: period).call
286
- end
287
- if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:adx)
288
- return TechnicalAnalysis.adx(high: high, low: low, close: close, period: period)
289
- end
290
-
291
- simple_adx(high, low, close, period)
292
- end
293
-
294
- def atr(high, low, close, period)
295
- if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:ATR)
296
- return RubyTechnicalAnalysis::ATR.new(high: high, low: low, close: close, period: period).call
297
- end
298
- if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:atr)
299
- return TechnicalAnalysis.atr(high: high, low: low, close: close, period: period)
300
- end
301
-
302
- simple_atr(high, low, close, period)
303
- end
304
-
305
- # ---- Simple fallbacks ----
306
- def ema(series, period)
307
- return nil if series.nil? || series.empty?
308
-
309
- k = 2.0 / (period + 1)
310
- series.each_with_index.reduce(nil) do |ema_prev, (v, i)|
311
- i == 0 ? v.to_f : (v.to_f * k) + ((ema_prev || v.to_f) * (1 - k))
312
- end
313
- end
314
-
315
- def simple_rsi(series, period)
316
- gains = []
317
- losses = []
318
- series.each_cons(2) do |a, b|
319
- ch = b - a
320
- gains << [ch, 0].max
321
- losses << [(-ch), 0].max
322
- end
323
- avg_gain = gains.first(period).sum / period.to_f
324
- avg_loss = losses.first(period).sum / period.to_f
325
- rsi_vals = Array.new(series.size, nil)
326
- gains.drop(period).each_with_index do |g, idx|
327
- l = losses[period + idx]
328
- avg_gain = ((avg_gain * (period - 1)) + g) / period
329
- avg_loss = ((avg_loss * (period - 1)) + l) / period
330
- rs = avg_loss.zero? ? 100.0 : (avg_gain / avg_loss)
331
- rsi_vals[period + 1 + idx] = 100.0 - (100.0 / (1 + rs))
332
- end
333
- rsi_vals
334
- end
335
-
336
- def simple_macd(series, fast, slow, signal)
337
- e_fast = ema(series, fast)
338
- e_slow = ema(series, slow)
339
- e_sig = ema(series, signal)
340
- return { macd: nil, signal: nil, hist: nil } if [e_fast, e_slow, e_sig].any?(&:nil?)
341
-
342
- macd_line = e_fast - e_slow
343
- signal_line = e_sig
344
- { macd: macd_line, signal: signal_line, hist: macd_line - signal_line }
345
- end
346
-
347
- def true_ranges(high, low, close)
348
- trs = []
349
- close.each_with_index do |_c, i|
350
- if i.zero?
351
- trs << (high[i] - low[i]).abs
352
- else
353
- tr = [(high[i] - low[i]).abs, (high[i] - close[i - 1]).abs, (low[i] - close[i - 1]).abs].max
354
- trs << tr
355
- end
356
- end
357
- trs
358
- end
359
-
360
- def simple_atr(high, low, close, period)
361
- trs = true_ranges(high, low, close)
362
- out = []
363
- atr_prev = trs.first(period).sum / period.to_f
364
- trs.each_with_index do |tr, i|
365
- if i < period
366
- out << nil
367
- elsif i == period
368
- out << atr_prev
369
- else
370
- atr_prev = ((atr_prev * (period - 1)) + tr) / period.to_f
371
- out << atr_prev
372
- end
373
- end
374
- out
375
- end
376
-
377
- def simple_adx(high, low, close, period)
378
- plus_dm = [0]
379
- minus_dm = [0]
380
- (1...high.size).each do |i|
381
- up_move = high[i] - high[i - 1]
382
- down_move = low[i - 1] - low[i]
383
- plus_dm << (up_move > down_move && up_move.positive? ? up_move : 0)
384
- minus_dm << (down_move > up_move && down_move.positive? ? down_move : 0)
385
- end
386
- trs = true_ranges(high, low, close)
387
- smooth_tr = trs.first(period).sum
388
- smooth_plus_dm = plus_dm.first(period).sum
389
- smooth_minus_dm = minus_dm.first(period).sum
390
- adx_vals = Array.new(high.size, nil)
391
- di_vals = []
392
- (period...high.size).each do |i|
393
- smooth_tr = smooth_tr - (smooth_tr / period) + trs[i]
394
- smooth_plus_dm = smooth_plus_dm - (smooth_plus_dm / period) + plus_dm[i]
395
- smooth_minus_dm = smooth_minus_dm - (smooth_minus_dm / period) + minus_dm[i]
396
- plus_di = 100.0 * (smooth_plus_dm / smooth_tr)
397
- minus_di = 100.0 * (smooth_minus_dm / smooth_tr)
398
- dx = 100.0 * ((plus_di - minus_di).abs / (plus_di + minus_di))
399
- di_vals << dx
400
- adx_vals[i] = di_vals.last(period).sum / period.to_f if di_vals.size >= period
401
- end
402
- adx_vals
403
- end
404
195
  end
405
196
  end
data/lib/ta.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ta/market_calendar"
4
+ require_relative "ta/candles"
5
+ require_relative "ta/indicators"
6
+ require_relative "ta/fetcher"
7
+ require_relative "ta/technical_analysis"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: DhanHQ
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shubham Taywade
@@ -145,6 +145,7 @@ extra_rdoc_files: []
145
145
  files:
146
146
  - ".rspec"
147
147
  - ".rubocop.yml"
148
+ - ".rubocop_todo.yml"
148
149
  - CHANGELOG.md
149
150
  - CODE_OF_CONDUCT.md
150
151
  - GUIDE.md
@@ -162,17 +163,18 @@ files:
162
163
  - diagram.html
163
164
  - diagram.md
164
165
  - docs/rails_integration.md
166
+ - docs/technical_analysis.md
165
167
  - exe/DhanHQ
166
- - lib/DhanHQ.rb
167
168
  - lib/DhanHQ/client.rb
168
169
  - lib/DhanHQ/config.rb
169
170
  - lib/DhanHQ/configuration.rb
170
171
  - lib/DhanHQ/constants.rb
171
172
  - lib/DhanHQ/contracts/base_contract.rb
172
173
  - lib/DhanHQ/contracts/historical_data_contract.rb
174
+ - lib/DhanHQ/contracts/instrument_list_contract.rb
173
175
  - lib/DhanHQ/contracts/margin_calculator_contract.rb
174
- - lib/DhanHQ/contracts/modify_order_contract copy.rb
175
176
  - lib/DhanHQ/contracts/modify_order_contract.rb
177
+ - lib/DhanHQ/contracts/modify_order_contract_copy.rb
176
178
  - lib/DhanHQ/contracts/option_chain_contract.rb
177
179
  - lib/DhanHQ/contracts/order_contract.rb
178
180
  - lib/DhanHQ/contracts/place_order_contract.rb
@@ -196,6 +198,7 @@ files:
196
198
  - lib/DhanHQ/models/funds.rb
197
199
  - lib/DhanHQ/models/historical_data.rb
198
200
  - lib/DhanHQ/models/holding.rb
201
+ - lib/DhanHQ/models/instrument.rb
199
202
  - lib/DhanHQ/models/kill_switch.rb
200
203
  - lib/DhanHQ/models/ledger_entry.rb
201
204
  - lib/DhanHQ/models/margin.rb
@@ -215,6 +218,7 @@ files:
215
218
  - lib/DhanHQ/resources/funds.rb
216
219
  - lib/DhanHQ/resources/historical_data.rb
217
220
  - lib/DhanHQ/resources/holdings.rb
221
+ - lib/DhanHQ/resources/instruments.rb
218
222
  - lib/DhanHQ/resources/kill_switch.rb
219
223
  - lib/DhanHQ/resources/margin_calculator.rb
220
224
  - lib/DhanHQ/resources/market_feed.rb
@@ -231,7 +235,6 @@ files:
231
235
  - lib/DhanHQ/ws/cmd_bus.rb
232
236
  - lib/DhanHQ/ws/connection.rb
233
237
  - lib/DhanHQ/ws/decoder.rb
234
- - lib/DhanHQ/ws/errors.rb
235
238
  - lib/DhanHQ/ws/orders.rb
236
239
  - lib/DhanHQ/ws/orders/client.rb
237
240
  - lib/DhanHQ/ws/orders/connection.rb
@@ -251,6 +254,17 @@ files:
251
254
  - lib/DhanHQ/ws/singleton_lock.rb
252
255
  - lib/DhanHQ/ws/sub_state.rb
253
256
  - lib/DhanHQ/ws/websocket_packet_parser.rb
257
+ - lib/dhan_hq.rb
258
+ - lib/dhanhq/analysis/helpers/bias_aggregator.rb
259
+ - lib/dhanhq/analysis/helpers/moneyness_helper.rb
260
+ - lib/dhanhq/analysis/multi_timeframe_analyzer.rb
261
+ - lib/dhanhq/analysis/options_buying_advisor.rb
262
+ - lib/dhanhq/contracts/options_buying_advisor_contract.rb
263
+ - lib/ta.rb
264
+ - lib/ta/candles.rb
265
+ - lib/ta/fetcher.rb
266
+ - lib/ta/indicators.rb
267
+ - lib/ta/market_calendar.rb
254
268
  - lib/ta/technical_analysis.rb
255
269
  - sig/DhanHQ.rbs
256
270
  - watchlist.csv
File without changes