DhanHQ 3.0.0 → 3.0.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +7 -0
  3. data/lib/DhanHQ/agent/order_preview.rb +50 -0
  4. data/lib/DhanHQ/agent/policy.rb +51 -0
  5. data/lib/DhanHQ/agent/tool_registry.rb +250 -0
  6. data/lib/DhanHQ/agent.rb +12 -0
  7. data/lib/DhanHQ/ai/context_builder.rb +145 -0
  8. data/lib/DhanHQ/ai/prompt_helpers.rb +114 -0
  9. data/lib/DhanHQ/ai.rb +27 -0
  10. data/lib/DhanHQ/auth.rb +0 -1
  11. data/lib/DhanHQ/client.rb +1 -3
  12. data/lib/DhanHQ/constants.rb +2 -0
  13. data/lib/DhanHQ/contracts/iceberg_order_contract.rb +83 -0
  14. data/lib/DhanHQ/contracts/twap_order_contract.rb +106 -0
  15. data/lib/DhanHQ/core/auth_api.rb +0 -1
  16. data/lib/DhanHQ/errors.rb +4 -0
  17. data/lib/DhanHQ/events/base.rb +203 -0
  18. data/lib/DhanHQ/events/bus.rb +158 -0
  19. data/lib/DhanHQ/events.rb +40 -0
  20. data/lib/DhanHQ/indicators.rb +283 -0
  21. data/lib/DhanHQ/market_data/market_snapshot.rb +97 -0
  22. data/lib/DhanHQ/market_data/ohlc_series.rb +169 -0
  23. data/lib/DhanHQ/market_data/option_snapshot.rb +223 -0
  24. data/lib/DhanHQ/market_data.rb +25 -0
  25. data/lib/DhanHQ/mcp/server.rb +72 -0
  26. data/lib/DhanHQ/mcp.rb +10 -0
  27. data/lib/DhanHQ/models/funds.rb +12 -0
  28. data/lib/DhanHQ/models/holding.rb +42 -0
  29. data/lib/DhanHQ/models/iceberg_order.rb +139 -0
  30. data/lib/DhanHQ/models/instrument.rb +36 -0
  31. data/lib/DhanHQ/models/order.rb +95 -0
  32. data/lib/DhanHQ/models/position.rb +66 -0
  33. data/lib/DhanHQ/models/search_result.rb +12 -0
  34. data/lib/DhanHQ/models/trade.rb +13 -0
  35. data/lib/DhanHQ/models/twap_order.rb +136 -0
  36. data/lib/DhanHQ/option_analytics/black_scholes.rb +194 -0
  37. data/lib/DhanHQ/option_analytics/max_pain.rb +119 -0
  38. data/lib/DhanHQ/option_analytics.rb +36 -0
  39. data/lib/DhanHQ/resources/iceberg_orders.rb +61 -0
  40. data/lib/DhanHQ/resources/twap_orders.rb +61 -0
  41. data/lib/DhanHQ/risk/checks/asm_gsm.rb +17 -0
  42. data/lib/DhanHQ/risk/checks/market_hours.rb +37 -0
  43. data/lib/DhanHQ/risk/checks/options.rb +46 -0
  44. data/lib/DhanHQ/risk/checks/order_type.rb +20 -0
  45. data/lib/DhanHQ/risk/checks/product_support.rb +34 -0
  46. data/lib/DhanHQ/risk/checks/quantity.rb +32 -0
  47. data/lib/DhanHQ/risk/checks/trading_permission.rb +16 -0
  48. data/lib/DhanHQ/risk/pipeline.rb +65 -0
  49. data/lib/DhanHQ/risk.rb +250 -0
  50. data/lib/DhanHQ/skills/base.rb +132 -0
  51. data/lib/DhanHQ/skills/builtin/buy_atm_call.rb +87 -0
  52. data/lib/DhanHQ/skills/builtin/iron_condor.rb +93 -0
  53. data/lib/DhanHQ/skills/builtin/square_off_all.rb +45 -0
  54. data/lib/DhanHQ/skills/builtin/square_off_position.rb +48 -0
  55. data/lib/DhanHQ/skills/builtin/strangle.rb +93 -0
  56. data/lib/DhanHQ/skills/registry.rb +101 -0
  57. data/lib/DhanHQ/skills/workflow.rb +66 -0
  58. data/lib/DhanHQ/skills.rb +29 -0
  59. data/lib/DhanHQ/strategy/base.rb +189 -0
  60. data/lib/DhanHQ/strategy.rb +40 -0
  61. data/lib/DhanHQ/version.rb +1 -1
  62. data/lib/DhanHQ/ws/decoder.rb +57 -19
  63. data/lib/DhanHQ.rb +3 -0
  64. data/lib/dhan_hq/agent.rb +3 -0
  65. data/lib/dhan_hq/mcp.rb +3 -0
  66. data/lib/dhan_hq.rb +27 -2
  67. data/lib/ta/technical_analysis.rb +3 -1
  68. data/skills/dhanhq-ruby/SKILL.md +74 -0
  69. data/skills/dhanhq-ruby/references/market_data.md +3 -0
  70. data/skills/dhanhq-ruby/references/orders.md +7 -0
  71. metadata +59 -20
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DhanHQ
4
+ # Strategy framework for building and backtesting trading strategies.
5
+ #
6
+ # Provides a DSL for defining entry/exit rules, risk management,
7
+ # and signal generation.
8
+ #
9
+ # @example Define a simple strategy
10
+ # class MyStrategy < DhanHQ::Strategy::Base
11
+ # entry_rule :golden_cross do |data|
12
+ # data.sma(20).last > data.sma(50).last &&
13
+ # data.sma(20).last(2) <= data.sma(50).last(2)
14
+ # end
15
+ #
16
+ # exit_rule :death_cross do |data|
17
+ # data.sma(20).last < data.sma(50).last
18
+ # end
19
+ # end
20
+ #
21
+ module Strategy
22
+ # Signal represents a trading signal generated by a strategy.
23
+ #
24
+ # @attr_reader [Symbol] type Signal type (:buy, :sell, :hold)
25
+ # @attr_reader [Float] strength Signal strength (0.0 to 1.0)
26
+ # @attr_reader [Hash] metadata Additional signal metadata
27
+ Signal = Struct.new(:type, :strength, :metadata) do
28
+ def buy?
29
+ type == :buy
30
+ end
31
+
32
+ def sell?
33
+ type == :sell
34
+ end
35
+
36
+ def hold?
37
+ type == :hold
38
+ end
39
+
40
+ def to_prompt
41
+ parts = ["signal=#{type}", "strength=#{strength}"]
42
+ parts << "reason=#{metadata[:reason]}" if metadata[:reason]
43
+ parts.join(", ")
44
+ end
45
+ end
46
+
47
+ # Base class for all strategies.
48
+ #
49
+ # Provides DSL for defining entry/exit rules and risk management.
50
+ class Base
51
+ attr_reader :name, :params, :position
52
+
53
+ def initialize(name: self.class.name, params: {})
54
+ @name = name
55
+ @params = params
56
+ @position = nil
57
+ @entry_rules = {}
58
+ @exit_rules = {}
59
+ @risk_rules = {}
60
+ @signals = []
61
+ end
62
+
63
+ # Define an entry rule.
64
+ #
65
+ # @param name [Symbol] Rule name
66
+ # @param priority [Integer] Rule priority (lower = higher priority)
67
+ # @param block [Proc] Rule condition block
68
+ def self.entry_rule(name, priority: 10, &block)
69
+ @entry_rules ||= {}
70
+ @entry_rules[name] = { priority: priority, block: block }
71
+ end
72
+
73
+ # Define an exit rule.
74
+ #
75
+ # @param name [Symbol] Rule name
76
+ # @param priority [Integer] Rule priority (lower = higher priority)
77
+ # @param block [Proc] Rule condition block
78
+ def self.exit_rule(name, priority: 10, &block)
79
+ @exit_rules ||= {}
80
+ @exit_rules[name] = { priority: priority, block: block }
81
+ end
82
+
83
+ # Define a risk rule.
84
+ #
85
+ # @param name [Symbol] Rule name
86
+ # @param block [Proc] Risk check block
87
+ def self.risk_rule(name, &block)
88
+ @risk_rules ||= {}
89
+ @risk_rules[name] = block
90
+ end
91
+
92
+ # Get all entry rules for this strategy class.
93
+ def self.entry_rules
94
+ @entry_rules || {}
95
+ end
96
+
97
+ # Get all exit rules for this strategy class.
98
+ def self.exit_rules
99
+ @exit_rules || {}
100
+ end
101
+
102
+ # Get all risk rules for this strategy class.
103
+ def self.risk_rules
104
+ @risk_rules || {}
105
+ end
106
+
107
+ # Evaluate entry rules against market data.
108
+ #
109
+ # @param data [DhanHQ::MarketData::OHLCSeries] Market data series
110
+ # @return [DhanHQ::Strategy::Signal] Generated signal
111
+ def evaluate_entry(data)
112
+ sorted_rules = self.class.entry_rules.sort_by { |_, v| v[:priority] }
113
+
114
+ sorted_rules.each do |rule_name, rule|
115
+ result = rule[:block].call(data, params)
116
+ next unless result
117
+
118
+ signal = Signal.new(
119
+ type: :buy,
120
+ strength: calculate_strength(result),
121
+ metadata: { rule: rule_name, data: data }
122
+ )
123
+ @signals << signal
124
+ return signal
125
+ end
126
+
127
+ Signal.new(type: :hold, strength: 0.0, metadata: { reason: :no_entry_signal })
128
+ end
129
+
130
+ # Evaluate exit rules against market data.
131
+ #
132
+ # @param data [DhanHQ::MarketData::OHLCSeries] Market data series
133
+ # @return [DhanHQ::Strategy::Signal] Generated signal
134
+ def evaluate_exit(data)
135
+ sorted_rules = self.class.exit_rules.sort_by { |_, v| v[:priority] }
136
+
137
+ sorted_rules.each do |rule_name, rule|
138
+ result = rule[:block].call(data, params)
139
+ next unless result
140
+
141
+ signal = Signal.new(
142
+ type: :sell,
143
+ strength: calculate_strength(result),
144
+ metadata: { rule: rule_name, data: data }
145
+ )
146
+ @signals << signal
147
+ return signal
148
+ end
149
+
150
+ Signal.new(type: :hold, strength: 0.0, metadata: { reason: :no_exit_signal })
151
+ end
152
+
153
+ # Check all risk rules.
154
+ #
155
+ # @param context [Hash] Risk check context
156
+ # @return [Array<Symbol>] List of violated rule names
157
+ def check_risks(context)
158
+ violations = []
159
+
160
+ self.class.risk_rules.each do |rule_name, block|
161
+ result = block.call(context, params)
162
+ violations << rule_name unless result
163
+ end
164
+
165
+ violations
166
+ end
167
+
168
+ # Get all signals generated by this strategy.
169
+ def signals
170
+ @signals.dup
171
+ end
172
+
173
+ # Get the last signal.
174
+ def last_signal
175
+ @signals.last
176
+ end
177
+
178
+ private
179
+
180
+ def calculate_strength(result)
181
+ case result
182
+ when true then 1.0
183
+ when Numeric then result.clamp(0.0, 1.0)
184
+ else 0.5
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "strategy/base"
4
+
5
+ module DhanHQ
6
+ # Strategy framework for building and backtesting trading strategies.
7
+ #
8
+ # Provides a DSL for defining entry/exit rules, risk management,
9
+ # and signal generation.
10
+ #
11
+ # @example Define a simple strategy
12
+ # class GoldenCross < DhanHQ::Strategy::Base
13
+ # entry_rule :golden_cross do |data, _params|
14
+ # sma_20 = DhanHQ::Indicators::SMA.calculate(data.closes, period: 20)
15
+ # sma_50 = DhanHQ::Indicators::SMA.calculate(data.closes, period: 50)
16
+ #
17
+ # sma_20.last && sma_50.last &&
18
+ # sma_20.last > sma_50.last &&
19
+ # sma_20[-2] && sma_50[-2] &&
20
+ # sma_20[-2] <= sma_50[-2]
21
+ # end
22
+ #
23
+ # exit_rule :death_cross do |data, _params|
24
+ # sma_20 = DhanHQ::Indicators::SMA.calculate(data.closes, period: 20)
25
+ # sma_50 = DhanHQ::Indicators::SMA.calculate(data.closes, period: 50)
26
+ #
27
+ # sma_20.last && sma_50.last && sma_20.last < sma_50.last
28
+ # end
29
+ #
30
+ # risk_rule :max_drawdown do |context, _params|
31
+ # context[:drawdown].abs < 0.1
32
+ # end
33
+ # end
34
+ #
35
+ # strategy = GoldenCross.new
36
+ # signal = strategy.evaluate_entry(data)
37
+ #
38
+ module Strategy
39
+ end
40
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DhanHQ
4
4
  # Semantic version of the DhanHQ client gem.
5
- VERSION = "3.0.0"
5
+ VERSION = "3.0.1"
6
6
  end
@@ -26,21 +26,45 @@ module DhanHQ
26
26
  segstr = Segments.from_code(pkt[:exchange_segment])
27
27
  sid = pkt[:security_id].to_s
28
28
 
29
- # pp pkt
30
- case kind
31
- when :ticker
29
+ dispatch(pkt, kind, segstr, sid)
30
+ rescue StandardError => e
31
+ DhanHQ.logger&.debug("[DhanHQ::WS::Decoder] #{e.class}: #{e.message}")
32
+ nil
33
+ end
34
+
35
+ class << self
36
+ private
37
+
38
+ def dispatch(pkt, kind, segstr, sid)
39
+ case kind
40
+ when :ticker then decode_ticker(pkt, segstr, sid)
41
+ when :quote then decode_quote(pkt, segstr, sid)
42
+ when :full then decode_full(pkt, segstr, sid)
43
+ when :oi then decode_oi(pkt, segstr, sid)
44
+ when :prev_close then decode_prev_close(pkt, segstr, sid)
45
+ when :depth_bid, :depth_ask then decode_depth(pkt, kind, segstr, sid)
46
+ when :disconnect then handle_disconnect(pkt, segstr, sid)
47
+ else handle_unknown(pkt)
48
+ end
49
+ end
50
+
51
+ def decode_ticker(pkt, segstr, sid)
32
52
  {
33
53
  kind: :ticker, segment: segstr, security_id: sid,
34
54
  ltp: pkt[:ltp].to_f, ts: pkt[:ltt]&.to_i
35
55
  }
36
- when :quote
56
+ end
57
+
58
+ def decode_quote(pkt, segstr, sid)
37
59
  {
38
60
  kind: :quote, segment: segstr, security_id: sid,
39
61
  ltp: pkt[:ltp].to_f, ts: pkt[:ltt]&.to_i, atp: pkt[:atp].to_f,
40
62
  vol: pkt[:volume].to_i, ts_buy_qty: pkt[:total_buy_qty].to_i, ts_sell_qty: pkt[:total_sell_qty].to_i,
41
63
  day_open: pkt[:day_open]&.to_f, day_high: pkt[:day_high]&.to_f, day_low: pkt[:day_low]&.to_f, day_close: pkt[:day_close]&.to_f
42
64
  }
43
- when :full
65
+ end
66
+
67
+ def decode_full(pkt, segstr, sid)
44
68
  out = {
45
69
  kind: :full, segment: segstr, security_id: sid,
46
70
  ltp: pkt[:ltp].to_f, ts: pkt[:ltt]&.to_i, atp: pkt[:atp].to_f,
@@ -48,35 +72,49 @@ module DhanHQ
48
72
  oi: pkt[:open_interest]&.to_i, oi_high: pkt[:highest_open_interest]&.to_i, oi_low: pkt[:lowest_open_interest]&.to_i,
49
73
  day_open: pkt[:day_open]&.to_f, day_high: pkt[:day_high]&.to_f, day_low: pkt[:day_low]&.to_f, day_close: pkt[:day_close]&.to_f
50
74
  }
51
- # First depth level (if present)
52
- if (md = pkt[:market_depth]).respond_to?(:[]) && md[0]
53
- lvl = md[0]
54
- out[:bid] = lvl.respond_to?(:bid_price) ? lvl.bid_price.to_f : nil
55
- out[:ask] = lvl.respond_to?(:ask_price) ? lvl.ask_price.to_f : nil
56
- end
75
+ merge_depth(out, pkt[:market_depth])
57
76
  out
58
- when :oi
77
+ end
78
+
79
+ def decode_oi(pkt, segstr, sid)
59
80
  { kind: :oi, segment: segstr, security_id: sid, oi: pkt[:open_interest].to_i }
60
- when :prev_close
81
+ end
82
+
83
+ def decode_prev_close(pkt, segstr, sid)
61
84
  { kind: :prev_close, segment: segstr, security_id: sid, prev_close: pkt[:prev_close].to_f,
62
85
  oi_prev: pkt[:oi_prev].to_i }
63
- when :depth_bid, :depth_ask
86
+ end
87
+
88
+ def decode_depth(pkt, kind, segstr, sid)
64
89
  {
65
90
  kind: kind, segment: segstr, security_id: sid,
66
91
  bid_quantity: pkt[:bid_quantity], ask_quantity: pkt[:ask_quantity],
67
92
  no_of_bid_orders: pkt[:no_of_bid_orders], no_of_ask_orders: pkt[:no_of_ask_orders],
68
93
  bid: pkt[:bid_price], ask: pkt[:ask_price]
69
94
  }
70
- when :disconnect
95
+ end
96
+
97
+ def handle_disconnect(pkt, segstr, sid)
71
98
  DhanHQ.logger&.warn("[DhanHQ::WS] disconnect code=#{pkt[:disconnection_code]} seg=#{segstr} sid=#{sid}")
72
99
  nil
73
- else
100
+ end
101
+
102
+ def handle_unknown(pkt)
74
103
  DhanHQ.logger&.debug("[DhanHQ::WS] unknown feed kind code=#{pkt[:feed_response_code]}")
75
104
  nil
76
105
  end
77
- rescue StandardError => e
78
- DhanHQ.logger&.debug("[DhanHQ::WS::Decoder] #{e.class}: #{e.message}")
79
- nil
106
+
107
+ def merge_depth(out, market_depth)
108
+ return unless market_depth.respond_to?(:[]) && market_depth[0]
109
+
110
+ lvl = market_depth[0]
111
+ out[:bid] = lvl.respond_to?(:bid_price) ? lvl.bid_price.to_f : nil
112
+ out[:ask] = lvl.respond_to?(:ask_price) ? lvl.ask_price.to_f : nil
113
+ return unless lvl.respond_to?(:bid_quantity) && lvl.respond_to?(:ask_quantity)
114
+
115
+ out[:bid_qty] = lvl.bid_quantity.to_i
116
+ out[:ask_qty] = lvl.ask_quantity.to_i
117
+ end
80
118
  end
81
119
  end
82
120
  end
data/lib/DhanHQ.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dhan_hq"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../DhanHQ/agent"
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../DhanHQ/mcp"
data/lib/dhan_hq.rb CHANGED
@@ -38,10 +38,35 @@ module DhanHQ
38
38
  LOADER.collapse(File.join(__dir__, "DhanHQ", "helpers"))
39
39
  LOADER.ignore(
40
40
  File.join(__dir__, "DhanHQ", "errors.rb"),
41
- File.join(__dir__, "DhanHQ", "version.rb")
41
+ File.join(__dir__, "DhanHQ", "version.rb"),
42
+ File.join(__dir__, "DhanHQ", "risk.rb")
42
43
  )
43
44
  LOADER.setup
44
45
 
46
+ # Eager-load risk utilities (PositionSizer, SLCalculator, TrailManager)
47
+ # and the pre-execution risk pipeline so they are available immediately.
48
+ require_relative "DhanHQ/risk"
49
+ require_relative "DhanHQ/risk/checks/trading_permission"
50
+ require_relative "DhanHQ/risk/checks/asm_gsm"
51
+ require_relative "DhanHQ/risk/checks/product_support"
52
+ require_relative "DhanHQ/risk/checks/order_type"
53
+ require_relative "DhanHQ/risk/checks/quantity"
54
+ require_relative "DhanHQ/risk/checks/market_hours"
55
+ require_relative "DhanHQ/risk/checks/options"
56
+ require_relative "DhanHQ/risk/pipeline"
57
+
58
+ # Skills layer: multi-step trading workflows
59
+ require_relative "DhanHQ/skills"
60
+ require_relative "DhanHQ/skills/base"
61
+ require_relative "DhanHQ/skills/registry"
62
+ require_relative "DhanHQ/skills/workflow"
63
+ require_relative "DhanHQ/skills/builtin/buy_atm_call"
64
+ require_relative "DhanHQ/skills/builtin/square_off_all"
65
+ require_relative "DhanHQ/skills/builtin/square_off_position"
66
+ require_relative "DhanHQ/skills/builtin/iron_condor"
67
+ require_relative "DhanHQ/skills/builtin/strangle"
68
+ DhanHQ::Skills::Registry.load_builtins
69
+
45
70
  class Error < StandardError; end
46
71
 
47
72
  class << self
@@ -131,7 +156,7 @@ module DhanHQ
131
156
 
132
157
  url = "#{base_url.to_s.chomp("/")}/auth/dhan/token"
133
158
  conn = ::Faraday.new(url: url) do |c|
134
- c.response :json, content_type: /\bjson$/
159
+ c.request :url_encoded
135
160
  c.adapter ::Faraday.default_adapter
136
161
  end
137
162
 
@@ -125,7 +125,9 @@ module TA
125
125
  def normalize_from_date(from_date, to_date, days_back)
126
126
  if (from_date.nil? || from_date.to_s.strip.empty?) && days_back&.to_i&.positive?
127
127
  to_d = Date.parse(to_date)
128
- n_back = [days_back.to_i - 1, 0].max
128
+ # Dhan historical API to_date is non-inclusive. To get N trading days,
129
+ # we must go back N trading days from the non-inclusive to_date.
130
+ n_back = days_back.to_i
129
131
  return MarketCalendar.trading_days_ago(to_d, n_back).strftime("%Y-%m-%d")
130
132
  end
131
133
  if from_date && !from_date.to_s.strip.empty?
@@ -0,0 +1,74 @@
1
+ # DhanHQ Ruby SDK Skill
2
+
3
+ Use this skill when an agent needs to write, review, or operate Ruby code using the `DhanHQ` gem.
4
+
5
+ ## Safety rules
6
+
7
+ - Prefer read-only tools and SDK calls unless the user explicitly asks to trade.
8
+ - Resolve instruments before trading with `DhanHQ::Models::Instrument.search` and use the returned `security_id` plus `exchange_segment`.
9
+ - Preview every order with `DhanHQ::Agent::OrderPreview` before any live order placement.
10
+ - Never place, modify, or cancel orders unless both `DHANHQ_MCP_ENABLE_WRITES=true` and `LIVE_TRADING=true` are set and the user has clearly confirmed the action.
11
+ - Include a `correlation_id` for every agent-originated order.
12
+ - Do not ask for or print access tokens.
13
+
14
+ ## Setup
15
+
16
+ ```ruby
17
+ require "dhan_hq"
18
+ require "dhan_hq/agent"
19
+
20
+ DhanHQ.configure_with_env
21
+ ```
22
+
23
+ Required environment variables:
24
+
25
+ - `DHAN_CLIENT_ID`
26
+ - `DHAN_ACCESS_TOKEN`
27
+
28
+ Optional agent variables:
29
+
30
+ - `DHANHQ_AGENT_SCOPES`, comma-separated scopes such as `portfolio:read,market:read,orders:read`
31
+ - `DHANHQ_MCP_ENABLE_WRITES=true` for agent write tools
32
+ - `LIVE_TRADING=true` for any live trading write
33
+
34
+ ## Common calls
35
+
36
+ ```ruby
37
+ DhanHQ::Models::Profile.fetch
38
+ DhanHQ::Models::Funds.fetch
39
+ DhanHQ::Models::Holding.all
40
+ DhanHQ::Models::Position.all
41
+ DhanHQ::Models::Order.all
42
+ DhanHQ::Models::Trade.today
43
+ DhanHQ::Models::Instrument.search("RELIANCE", segments: ["NSE_EQ"], limit: 5)
44
+ DhanHQ::Models::MarketFeed.ltp("NSE_EQ" => ["2885"])
45
+ ```
46
+
47
+ ## Order preview
48
+
49
+ ```ruby
50
+ preview = DhanHQ::Agent::OrderPreview.new(
51
+ transaction_type: "BUY",
52
+ exchange_segment: "NSE_EQ",
53
+ product_type: "INTRADAY",
54
+ order_type: "MARKET",
55
+ validity: "DAY",
56
+ security_id: "2885",
57
+ quantity: 1,
58
+ correlation_id: "agent-20260702-001"
59
+ )
60
+
61
+ preview.to_h
62
+ ```
63
+
64
+ ## MCP server
65
+
66
+ The gem includes a stdio MCP executable:
67
+
68
+ ```bash
69
+ dhanhq-mcp
70
+ ```
71
+
72
+ Start with read-only scopes. Add write scopes only for explicitly confirmed trading sessions.
73
+
74
+ See the references directory for focused workflows.
@@ -0,0 +1,3 @@
1
+ # Market data with agents
2
+
3
+ Use `DhanHQ::Models::Instrument.search` to resolve a query to Dhan security IDs, then call `DhanHQ::Models::MarketFeed.ltp`, `.ohlc`, or `.quote` with an exchange-segment keyed payload.
@@ -0,0 +1,7 @@
1
+ # Orders with agents
2
+
3
+ 1. Search or otherwise verify the instrument.
4
+ 2. Build order params using Dhan constants and API docs.
5
+ 3. Run `DhanHQ::Agent::OrderPreview.new(params).to_h`.
6
+ 4. Ask the user to confirm the preview.
7
+ 5. Place only if `DHANHQ_MCP_ENABLE_WRITES=true`, `LIVE_TRADING=true`, and `orders:write` scope are present.