Class: GrowwMcp::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/groww_mcp/client.rb

Constant Summary collapse

BASE_URL =
"https://api.groww.in"
MAX_RETRIES =
3
RETRY_DELAY =
0.5
INSTRUMENTS_CACHE_PATH =
File.join(Dir.tmpdir, "groww-mcp-instruments.csv")
INSTRUMENTS_CACHE_TTL =

24 hours

24 * 60 * 60

Instance Method Summary collapse

Constructor Details

#initialize(auth) ⇒ Client

Returns a new instance of Client.



17
18
19
# File 'lib/groww_mcp/client.rb', line 17

def initialize(auth)
  @auth = auth
end

Instance Method Details

#calculate_margin(orders, segment:) ⇒ Object

Margin requirement for a basket of orders. Body is a JSON ARRAY of order objects; segment goes as a query param.



191
192
193
# File 'lib/groww_mcp/client.rb', line 191

def calculate_margin(orders, segment:)
  post("/v1/margins/detail/orders", orders, params: { segment: segment })
end

#cancel_order(order_id, segment: "CASH") ⇒ Object



61
62
63
# File 'lib/groww_mcp/client.rb', line 61

def cancel_order(order_id, segment: "CASH")
  post("/v1/order/cancel", groww_order_id: order_id, segment: segment)
end

#cancel_smart_order(smart_order_id, smart_order_type:, segment: "CASH") ⇒ Object



89
90
91
# File 'lib/groww_mcp/client.rb', line 89

def cancel_smart_order(smart_order_id, smart_order_type:, segment: "CASH")
  post("/v1/order-advance/cancel/#{segment}/#{smart_order_type}/#{smart_order_id}", {})
end

#create_smart_order(order) ⇒ Object



79
80
81
82
# File 'lib/groww_mcp/client.rb', line 79

def create_smart_order(order)
  order = { reference_id: generate_reference_id }.merge(order)
  post("/v1/order-advance/create", order)
end

#download_instrumentsObject



173
174
175
176
177
178
179
# File 'lib/groww_mcp/client.rb', line 173

def download_instruments
  uri = URI("https://growwapi-assets.groww.in/instruments/instrument.csv")
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  response = http.request(Net::HTTP::Get.new(uri))
  response.body
end

#greeks(trading_symbol:, underlying:, expiry:, exchange: "NSE") ⇒ Object

Greeks (requires ₹499/mo subscription) ===



115
116
117
118
# File 'lib/groww_mcp/client.rb', line 115

def greeks(trading_symbol:, underlying:, expiry:, exchange: "NSE")
  get("/v1/live-data/greeks/exchange/#{exchange}/underlying/#{underlying}" \
      "/trading_symbol/#{trading_symbol}/expiry/#{expiry}")
end

#historical_candles(trading_symbol, start_time, end_time, exchange: "NSE", segment: "CASH", interval_in_minutes: nil) ⇒ Object

Historical Data (requires ₹499/mo subscription) ===



122
123
124
125
126
127
128
129
130
# File 'lib/groww_mcp/client.rb', line 122

def historical_candles(trading_symbol, start_time, end_time, exchange: "NSE", segment: "CASH",
                       interval_in_minutes: nil)
  params = {
    trading_symbol: trading_symbol, exchange: exchange, segment: segment,
    start_time: normalize_time(start_time), end_time: normalize_time(end_time),
  }
  params[:interval_in_minutes] = interval_in_minutes if interval_in_minutes
  get("/v1/historical/candle/range", params)
end

#holdingsObject

Portfolio ===



23
24
25
# File 'lib/groww_mcp/client.rb', line 23

def holdings
  get("/v1/holdings/user")
end

#instrument_detail(trading_symbol, exchange: "NSE", segment: "CASH") ⇒ Object

Exact trading-symbol match (case-insensitive), filtered by exchange/segment. Returns the matching row as a Hash, or nil if not found.



162
163
164
165
166
167
168
169
170
171
# File 'lib/groww_mcp/client.rb', line 162

def instrument_detail(trading_symbol, exchange: "NSE", segment: "CASH")
  instruments.each do |row|
    next unless row["trading_symbol"].to_s.casecmp?(trading_symbol.to_s)
    next if exchange && !row["exchange"].to_s.empty? && !row["exchange"].casecmp?(exchange.to_s)
    next if segment && !row["segment"].to_s.empty? && !row["segment"].casecmp?(segment.to_s)

    return row.to_h
  end
  nil
end

#ltp(symbols, exchange: "NSE", segment: "CASH") ⇒ Object



99
100
101
# File 'lib/groww_mcp/client.rb', line 99

def ltp(symbols, exchange: "NSE", segment: "CASH")
  get("/v1/live-data/ltp", segment: segment, exchange_symbols: exchange_symbols(symbols, exchange))
end

#marginsObject



33
34
35
# File 'lib/groww_mcp/client.rb', line 33

def margins
  get("/v1/margins/detail/user")
end

#modify_order(order_id, modifications) ⇒ Object



57
58
59
# File 'lib/groww_mcp/client.rb', line 57

def modify_order(order_id, modifications)
  post("/v1/order/modify", modifications.merge(groww_order_id: order_id))
end

#modify_smart_order(smart_order_id, smart_order_type:, segment:, modifications: {}) ⇒ Object



84
85
86
87
# File 'lib/groww_mcp/client.rb', line 84

def modify_smart_order(smart_order_id, smart_order_type:, segment:, modifications: {})
  put("/v1/order-advance/modify/#{smart_order_id}",
      modifications.merge(smart_order_type: smart_order_type, segment: segment))
end

#ohlc(symbols, exchange: "NSE", segment: "CASH") ⇒ Object



103
104
105
# File 'lib/groww_mcp/client.rb', line 103

def ohlc(symbols, exchange: "NSE", segment: "CASH")
  get("/v1/live-data/ohlc", segment: segment, exchange_symbols: exchange_symbols(symbols, exchange))
end

#option_chain(underlying, expiry_date:, exchange: "NSE") ⇒ Object

Option Chain (requires ₹499/mo subscription) ===



109
110
111
# File 'lib/groww_mcp/client.rb', line 109

def option_chain(underlying, expiry_date:, exchange: "NSE")
  get("/v1/option-chain/exchange/#{exchange}/underlying/#{underlying}", expiry_date: expiry_date)
end

#order_detail(order_id, segment: "CASH") ⇒ Object



47
48
49
# File 'lib/groww_mcp/client.rb', line 47

def order_detail(order_id, segment: "CASH")
  get("/v1/order/detail/#{order_id}", segment: segment)
end

#order_list(segment: nil, page: nil, page_size: nil) ⇒ Object

Orders ===



39
40
41
42
43
44
45
# File 'lib/groww_mcp/client.rb', line 39

def order_list(segment: nil, page: nil, page_size: nil)
  params = {}
  params[:segment] = segment if segment
  params[:page] = page if page
  params[:page_size] = page_size if page_size
  get("/v1/order/list", params)
end

#order_trades(order_id, segment: "CASH") ⇒ Object



65
66
67
# File 'lib/groww_mcp/client.rb', line 65

def order_trades(order_id, segment: "CASH")
  get("/v1/order/trades/#{order_id}", segment: segment)
end

#place_order(order) ⇒ Object



51
52
53
54
55
# File 'lib/groww_mcp/client.rb', line 51

def place_order(order)
  # reference_id lets Groww dedupe if the same order is ever re-submitted
  order = { order_reference_id: generate_reference_id }.merge(order)
  post("/v1/order/create", order)
end

#positions(segment: nil) ⇒ Object



27
28
29
30
31
# File 'lib/groww_mcp/client.rb', line 27

def positions(segment: nil)
  path = "/v1/positions/user"
  path += "?segment=#{segment}" if segment
  get(path)
end

#profileObject

User ===



183
184
185
# File 'lib/groww_mcp/client.rb', line 183

def profile
  get("/v1/user/detail")
end

#quote(trading_symbol, exchange: "NSE", segment: "CASH") ⇒ Object

Live Data (requires ₹499/mo subscription) ===



95
96
97
# File 'lib/groww_mcp/client.rb', line 95

def quote(trading_symbol, exchange: "NSE", segment: "CASH")
  get("/v1/live-data/quote", trading_symbol: trading_symbol, exchange: exchange, segment: segment)
end

#search_instruments(query, limit: 20) ⇒ Object

Case-insensitive substring search on trading symbol and name. Runs entirely locally against the cached instruments CSV — no API call, no Live Data subscription needed.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/groww_mcp/client.rb', line 137

def search_instruments(query, limit: 20)
  q = query.to_s.downcase
  return [] if q.empty?

  matches = []
  instruments.each do |row|
    symbol = row["trading_symbol"].to_s.downcase
    name = row["name"].to_s.downcase
    matches << row if symbol.include?(q) || name.include?(q)
  end

  # Rank so the most useful rows surface first: exact symbol match,
  # then CASH (equity) before FNO contracts, then symbol-prefix matches.
  matches.sort_by! do |row|
    symbol = row["trading_symbol"].to_s.downcase
    [symbol == q ? 0 : 1,
     row["segment"] == "CASH" ? 0 : 1,
     symbol.start_with?(q) ? 0 : 1,
     symbol]
  end
  matches.first(limit).map(&:to_h)
end

#smart_order_list(smart_order_type: nil, page: nil, page_size: nil) ⇒ Object

Smart Orders (GTT/OCO) ===



71
72
73
74
75
76
77
# File 'lib/groww_mcp/client.rb', line 71

def smart_order_list(smart_order_type: nil, page: nil, page_size: nil)
  params = {}
  params[:smart_order_type] = smart_order_type if smart_order_type
  params[:page] = page if page
  params[:page_size] = page_size if page_size
  get("/v1/order-advance/list", params)
end