DhanHQ 2.6.2 → 2.6.3
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -3
- data/ARCHITECTURE.md +113 -0
- data/CHANGELOG.md +31 -0
- data/README.md +2 -0
- data/docs/API_VERIFICATION.md +10 -8
- data/docs/ENDPOINTS_AND_SANDBOX.md +12 -0
- data/lib/DhanHQ/auth.rb +2 -2
- data/lib/DhanHQ/client.rb +42 -34
- data/lib/DhanHQ/configuration.rb +5 -6
- data/lib/DhanHQ/constants.rb +67 -7
- data/lib/DhanHQ/contracts/alert_order_contract.rb +23 -16
- data/lib/DhanHQ/contracts/expired_options_data_contract.rb +4 -2
- data/lib/DhanHQ/contracts/forever_order_contract.rb +55 -0
- data/lib/DhanHQ/contracts/historical_data_contract.rb +17 -19
- data/lib/DhanHQ/contracts/intraday_historical_data_contract.rb +12 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +19 -17
- data/lib/DhanHQ/contracts/market_feed_contract.rb +42 -0
- data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +8 -5
- data/lib/DhanHQ/contracts/option_chain_contract.rb +17 -19
- data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +1 -1
- data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -10
- data/lib/DhanHQ/core/auth_api.rb +1 -1
- data/lib/DhanHQ/core/base_api.rb +9 -9
- data/lib/DhanHQ/core/base_model.rb +4 -1
- data/lib/DhanHQ/core/error_handler.rb +2 -2
- data/lib/DhanHQ/errors.rb +14 -2
- data/lib/DhanHQ/helpers/request_helper.rb +11 -2
- data/lib/DhanHQ/helpers/response_helper.rb +48 -19
- data/lib/DhanHQ/helpers/validation_helper.rb +4 -2
- data/lib/DhanHQ/models/alert_order.rb +6 -2
- data/lib/DhanHQ/models/edis.rb +20 -13
- data/lib/DhanHQ/models/expired_options_data.rb +54 -44
- data/lib/DhanHQ/models/forever_order.rb +16 -7
- data/lib/DhanHQ/models/historical_data.rb +40 -6
- data/lib/DhanHQ/models/instrument_helpers.rb +2 -1
- data/lib/DhanHQ/models/margin.rb +62 -82
- data/lib/DhanHQ/models/market_feed.rb +14 -3
- data/lib/DhanHQ/models/option_chain.rb +50 -150
- data/lib/DhanHQ/models/order.rb +19 -4
- data/lib/DhanHQ/resources/alert_orders.rb +1 -1
- data/lib/DhanHQ/resources/edis.rb +4 -3
- data/lib/DhanHQ/resources/instruments.rb +3 -2
- data/lib/DhanHQ/resources/ip_setup.rb +4 -1
- data/lib/DhanHQ/resources/kill_switch.rb +7 -1
- data/lib/DhanHQ/resources/orders.rb +1 -1
- data/lib/DhanHQ/resources/super_orders.rb +8 -2
- data/lib/DhanHQ/resources/trader_control.rb +13 -4
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/base_connection.rb +1 -1
- data/lib/DhanHQ/ws/market_depth/client.rb +11 -4
- data/lib/dhan_hq.rb +17 -20
- data/lib/ta/indicators.rb +15 -18
- metadata +6 -9
- data/CODE_REVIEW_ISSUES.md +0 -397
- data/FIXES_APPLIED.md +0 -373
- data/RELEASING.md +0 -60
- data/REVIEW_SUMMARY.md +0 -120
- data/VERSION_UPDATE.md +0 -82
- data/diagram.md +0 -34
- data/docs/ARCHIVE_README.md +0 -784
|
@@ -8,7 +8,7 @@ module DhanHQ
|
|
|
8
8
|
class AlertOrder < BaseModel
|
|
9
9
|
include Concerns::ApiResponseHandler
|
|
10
10
|
|
|
11
|
-
HTTP_PATH = "/alerts/orders"
|
|
11
|
+
HTTP_PATH = "/v2/alerts/orders"
|
|
12
12
|
|
|
13
13
|
attributes :alert_id, :exchange_segment, :security_id, :condition,
|
|
14
14
|
:trigger_price, :order_type, :transaction_type, :quantity,
|
|
@@ -37,6 +37,8 @@ module DhanHQ
|
|
|
37
37
|
return nil unless response.is_a?(Hash) || (response.is_a?(Array) && response.any?)
|
|
38
38
|
|
|
39
39
|
payload = response.is_a?(Array) ? response.first : response
|
|
40
|
+
return nil if payload.is_a?(Hash) && payload.empty?
|
|
41
|
+
|
|
40
42
|
new(payload, skip_validation: true)
|
|
41
43
|
end
|
|
42
44
|
|
|
@@ -65,7 +67,9 @@ module DhanHQ
|
|
|
65
67
|
#
|
|
66
68
|
def modify(alert_id, params)
|
|
67
69
|
normalized = snake_case(params)
|
|
68
|
-
|
|
70
|
+
validate_params!(normalized, DhanHQ::Contracts::AlertOrderContract)
|
|
71
|
+
payload = normalized.merge(alert_id: alert_id)
|
|
72
|
+
response = resource.update(alert_id, camelize_keys(payload))
|
|
69
73
|
return nil unless success_response?(response)
|
|
70
74
|
|
|
71
75
|
find(alert_id)
|
data/lib/DhanHQ/models/edis.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../contracts/edis_contract"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Models
|
|
5
7
|
##
|
|
@@ -16,15 +18,15 @@ module DhanHQ
|
|
|
16
18
|
# isin: "INE155A01022",
|
|
17
19
|
# qty: 10,
|
|
18
20
|
# exchange: "NSE",
|
|
19
|
-
# segment: "
|
|
21
|
+
# segment: "EQ",
|
|
20
22
|
# bulk: false
|
|
21
23
|
# )
|
|
22
24
|
#
|
|
23
|
-
# @example Check EDIS status for a security
|
|
25
|
+
# @example Check EDIS status for a security (or "ALL" for all holdings)
|
|
24
26
|
# status = DhanHQ::Models::Edis.inquire(isin: "INE155A01022")
|
|
25
27
|
#
|
|
26
28
|
class Edis < BaseModel
|
|
27
|
-
HTTP_PATH = "/edis"
|
|
29
|
+
HTTP_PATH = "/v2/edis"
|
|
28
30
|
|
|
29
31
|
class << self
|
|
30
32
|
##
|
|
@@ -52,24 +54,26 @@ module DhanHQ
|
|
|
52
54
|
##
|
|
53
55
|
# Generate an eDIS form for authorizing sale of holdings.
|
|
54
56
|
#
|
|
55
|
-
# @param isin [String] ISIN of the security (e.g
|
|
57
|
+
# @param isin [String] ISIN of the security (e.g. "INE733E01010")
|
|
56
58
|
# @param qty [Integer] Quantity to authorize for sale
|
|
57
|
-
# @param exchange [String] Exchange
|
|
58
|
-
# @param segment [String] Segment
|
|
59
|
-
# @param bulk [Boolean]
|
|
59
|
+
# @param exchange [String] Exchange: "NSE" or "BSE"
|
|
60
|
+
# @param segment [String] Segment: "EQ", "COMM", or "FNO"
|
|
61
|
+
# @param bulk [Boolean] If true, mark eDIS for all stocks in portfolio (default: false)
|
|
60
62
|
#
|
|
61
|
-
# @return [Hash] API response
|
|
63
|
+
# @return [Hash] API response with dhanClientId and edisFormHtml (escaped HTML to render)
|
|
62
64
|
#
|
|
63
65
|
# @example Authorize sale of 10 shares
|
|
64
66
|
# DhanHQ::Models::Edis.generate_form(
|
|
65
67
|
# isin: "INE155A01022",
|
|
66
68
|
# qty: 10,
|
|
67
69
|
# exchange: "NSE",
|
|
68
|
-
# segment: "
|
|
70
|
+
# segment: "EQ"
|
|
69
71
|
# )
|
|
70
72
|
#
|
|
71
73
|
def generate_form(isin:, qty:, exchange:, segment:, bulk: false)
|
|
72
|
-
|
|
74
|
+
params = { isin: isin, qty: qty, exchange: exchange, segment: segment, bulk: bulk }
|
|
75
|
+
validate_params!(params, DhanHQ::Contracts::EdisFormContract)
|
|
76
|
+
resource.form(params)
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
##
|
|
@@ -85,13 +89,16 @@ module DhanHQ
|
|
|
85
89
|
##
|
|
86
90
|
# Check EDIS authorization status for a security.
|
|
87
91
|
#
|
|
88
|
-
# @param isin [String] ISIN of the security
|
|
92
|
+
# @param isin [String] ISIN of the security, or "ALL" for all holdings
|
|
89
93
|
#
|
|
90
|
-
# @return [Hash] API response
|
|
94
|
+
# @return [Hash] API response with clientId, isin, totalQty, aprvdQty, status, remarks
|
|
91
95
|
#
|
|
92
|
-
# @example Check if EDIS is authorized
|
|
96
|
+
# @example Check if EDIS is authorized for one security
|
|
93
97
|
# status = DhanHQ::Models::Edis.inquire(isin: "INE155A01022")
|
|
94
98
|
#
|
|
99
|
+
# @example Check EDIS status for all holdings
|
|
100
|
+
# status = DhanHQ::Models::Edis.inquire(isin: "ALL")
|
|
101
|
+
#
|
|
95
102
|
def inquire(isin:)
|
|
96
103
|
resource.inquire(isin)
|
|
97
104
|
end
|
|
@@ -42,7 +42,12 @@ module DhanHQ
|
|
|
42
42
|
# call_data = data.call_data
|
|
43
43
|
# put_data = data.put_data
|
|
44
44
|
#
|
|
45
|
+
# @example Normalize to candles
|
|
46
|
+
# candles = data.to_candles
|
|
47
|
+
#
|
|
45
48
|
class ExpiredOptionsData < BaseModel
|
|
49
|
+
OHLC_FIELDS = %i[open high low close iv volume strike spot oi open_interest].freeze
|
|
50
|
+
|
|
46
51
|
# All expired options data attributes
|
|
47
52
|
attributes :exchange_segment, :interval, :security_id, :instrument,
|
|
48
53
|
:expiry_flag, :expiry_code, :strike, :drv_option_type,
|
|
@@ -56,63 +61,29 @@ module DhanHQ
|
|
|
56
61
|
# 31 days in a single request. Historical data is available for up to the last 5 years.
|
|
57
62
|
#
|
|
58
63
|
# @param params [Hash{Symbol => String, Integer, Array<String>}] Request parameters
|
|
64
|
+
# @option params [String, Integer] :security_id (required) Underlying exchange standard ID for each scrip
|
|
59
65
|
# @option params [String] :exchange_segment (required) Exchange and segment identifier.
|
|
60
|
-
# Valid values: "NSE_FNO", "
|
|
61
|
-
# @option params [String] :interval (required) Minute intervals for the timeframe.
|
|
62
|
-
# Valid values: "1", "5", "15", "25", "60"
|
|
63
|
-
# @option params [Integer] :security_id (required) Underlying exchange standard ID for each scrip
|
|
66
|
+
# Valid values: "NSE_FNO", "IDX_I", "NSE_EQ", "BSE_EQ"
|
|
64
67
|
# @option params [String] :instrument (required) Instrument type of the scrip.
|
|
65
68
|
# Valid values: "OPTIDX" (Index Options), "OPTSTK" (Stock Options)
|
|
69
|
+
# @option params [String, Integer] :interval (required) Minute intervals for the timeframe.
|
|
70
|
+
# Valid values: "1", "5", "15", "25", "60"
|
|
66
71
|
# @option params [String] :expiry_flag (required) Expiry interval of the instrument.
|
|
67
72
|
# Valid values: "WEEK", "MONTH"
|
|
68
73
|
# @option params [Integer] :expiry_code (required) Expiry code for the instrument
|
|
69
74
|
# @option params [String] :strike (required) Strike price specification.
|
|
70
75
|
# Format: "ATM" for At The Money, "ATM+X" or "ATM-X" for offset strikes.
|
|
71
|
-
#
|
|
72
|
-
# For all other contracts: Up to ATM+3 / ATM-3
|
|
73
|
-
# @option params [String] :drv_option_type (required) Option type.
|
|
74
|
-
# Valid values: "CALL", "PUT"
|
|
76
|
+
# @option params [String] :option_type (required) Option type ("CALL" or "PUT").
|
|
75
77
|
# @option params [Array<String>] :required_data (required) Array of required data fields.
|
|
76
|
-
#
|
|
77
|
-
# @option params [String] :
|
|
78
|
-
# Cannot be more than 5 years ago. Same-day ranges are allowed.
|
|
79
|
-
# @option params [String] :to_date (required) End date of the desired range (non-inclusive) in YYYY-MM-DD format.
|
|
80
|
-
# Date range cannot exceed 31 days from from_date (to_date is non-inclusive). Same-day `from_date`/`to_date` is valid.
|
|
78
|
+
# @option params [String] :from_date (required) Start date in YYYY-MM-DD format.
|
|
79
|
+
# @option params [String] :to_date (required) End date in YYYY-MM-DD format.
|
|
81
80
|
#
|
|
82
81
|
# @return [ExpiredOptionsData] Expired options data object with fetched data
|
|
83
|
-
#
|
|
84
|
-
# @example Fetch NIFTY index options data
|
|
85
|
-
# data = DhanHQ::Models::ExpiredOptionsData.fetch(
|
|
86
|
-
# exchange_segment: "NSE_FNO",
|
|
87
|
-
# interval: "1",
|
|
88
|
-
# security_id: 13,
|
|
89
|
-
# instrument: "OPTIDX",
|
|
90
|
-
# expiry_flag: "MONTH",
|
|
91
|
-
# expiry_code: 1,
|
|
92
|
-
# strike: "ATM",
|
|
93
|
-
# drv_option_type: "CALL",
|
|
94
|
-
# required_data: ["open", "high", "low", "close", "volume", "iv", "oi", "spot"],
|
|
95
|
-
# from_date: "2021-08-01",
|
|
96
|
-
# to_date: "2021-09-01"
|
|
97
|
-
# )
|
|
98
|
-
#
|
|
99
|
-
# @example Fetch stock options data for ATM+2 strike
|
|
100
|
-
# data = DhanHQ::Models::ExpiredOptionsData.fetch(
|
|
101
|
-
# exchange_segment: "NSE_FNO",
|
|
102
|
-
# interval: "15",
|
|
103
|
-
# security_id: 11536,
|
|
104
|
-
# instrument: "OPTSTK",
|
|
105
|
-
# expiry_flag: "WEEK",
|
|
106
|
-
# expiry_code: 0,
|
|
107
|
-
# strike: "ATM+2",
|
|
108
|
-
# drv_option_type: "PUT",
|
|
109
|
-
# required_data: ["open", "high", "low", "close", "volume"],
|
|
110
|
-
# from_date: "2024-01-01",
|
|
111
|
-
# to_date: "2024-01-31"
|
|
112
|
-
# )
|
|
113
|
-
#
|
|
114
82
|
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
115
83
|
def fetch(params)
|
|
84
|
+
# Map option_type to drv_option_type if provided
|
|
85
|
+
params[:drv_option_type] ||= params[:option_type] if params.key?(:option_type)
|
|
86
|
+
|
|
116
87
|
normalized = normalize_params(params)
|
|
117
88
|
validate_params(normalized)
|
|
118
89
|
|
|
@@ -120,6 +91,8 @@ module DhanHQ
|
|
|
120
91
|
new(response.merge(normalized), skip_validation: true)
|
|
121
92
|
end
|
|
122
93
|
|
|
94
|
+
alias rolling fetch
|
|
95
|
+
|
|
123
96
|
private
|
|
124
97
|
|
|
125
98
|
def expired_options_resource
|
|
@@ -180,6 +153,43 @@ module DhanHQ
|
|
|
180
153
|
end
|
|
181
154
|
end
|
|
182
155
|
|
|
156
|
+
##
|
|
157
|
+
# Normalizes the columnar response into an array of candle hashes.
|
|
158
|
+
#
|
|
159
|
+
# @param option_type [String, nil] Option type to retrieve ("CALL" or "PUT").
|
|
160
|
+
# If nil, uses the {#drv_option_type} from the request.
|
|
161
|
+
# @return [Array<Hash>] Normalized array of candles.
|
|
162
|
+
def to_candles(option_type = nil)
|
|
163
|
+
option_type ||= drv_option_type
|
|
164
|
+
opt_data = data_for_type(option_type)
|
|
165
|
+
return [] unless opt_data.is_a?(Hash)
|
|
166
|
+
|
|
167
|
+
# Standardize keys to symbols
|
|
168
|
+
opt_data = opt_data.transform_keys(&:to_sym)
|
|
169
|
+
ts_arr = opt_data[:timestamp]
|
|
170
|
+
return [] unless ts_arr.is_a?(Array)
|
|
171
|
+
|
|
172
|
+
type_sym = option_type.to_s.downcase.to_sym
|
|
173
|
+
|
|
174
|
+
ts_arr.each_with_index.map do |ts, i|
|
|
175
|
+
candle = {
|
|
176
|
+
option_type: type_sym,
|
|
177
|
+
timestamp: ts.is_a?(Numeric) ? Time.at(ts) : ts
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Map requested fields
|
|
181
|
+
OHLC_FIELDS.each do |field|
|
|
182
|
+
val_arr = opt_data[field]
|
|
183
|
+
next unless val_arr.is_a?(Array)
|
|
184
|
+
|
|
185
|
+
# Map 'oi' to 'open_interest' if requested
|
|
186
|
+
target_field = field == :oi ? :open_interest : field
|
|
187
|
+
candle[target_field] = val_arr[i]
|
|
188
|
+
end
|
|
189
|
+
candle
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
183
193
|
##
|
|
184
194
|
# Gets call option data from the response.
|
|
185
195
|
#
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../contracts/forever_order_contract"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Models
|
|
5
7
|
##
|
|
@@ -154,9 +156,9 @@ module DhanHQ
|
|
|
154
156
|
# @option params [String] :transaction_type (required) The trading side of transaction.
|
|
155
157
|
# Valid values: "BUY", "SELL"
|
|
156
158
|
# @option params [String] :exchange_segment (required) Exchange and segment identifier.
|
|
157
|
-
# Valid values:
|
|
159
|
+
# Valid values: See {DhanHQ::Constants::FOREVER_ORDER_SEGMENTS} (NSE_EQ, NSE_FNO, BSE_EQ, BSE_FNO, MCX_COMM)
|
|
158
160
|
# @option params [String] :product_type (required) Product type.
|
|
159
|
-
# Valid values:
|
|
161
|
+
# Valid values: See {DhanHQ::Constants::FOREVER_ORDER_PRODUCT_TYPES} (CNC, MTF)
|
|
160
162
|
# @option params [String] :order_type (required) Order type.
|
|
161
163
|
# Valid values: "LIMIT", "MARKET"
|
|
162
164
|
# @option params [String] :validity (required) Validity of order for execution.
|
|
@@ -216,11 +218,12 @@ module DhanHQ
|
|
|
216
218
|
#
|
|
217
219
|
# @note Order placement APIs require Static IP whitelisting
|
|
218
220
|
def create(params)
|
|
219
|
-
|
|
220
|
-
normalized_params = snake_case(params)
|
|
221
|
+
normalized = snake_case(params)
|
|
221
222
|
config = DhanHQ.configuration
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
normalized[:dhan_client_id] ||= config.client_id if config&.client_id
|
|
224
|
+
validate_params!(normalized, DhanHQ::Contracts::ForeverOrderCreateContract)
|
|
225
|
+
formatted = camelize_keys(normalized)
|
|
226
|
+
response = resource.create(formatted)
|
|
224
227
|
return nil unless response.is_a?(Hash) && response["orderId"]
|
|
225
228
|
|
|
226
229
|
find(response["orderId"])
|
|
@@ -292,7 +295,13 @@ module DhanHQ
|
|
|
292
295
|
raise "Order ID is required to modify a forever order" unless order_id
|
|
293
296
|
|
|
294
297
|
DhanHQ.logger&.info("[DhanHQ::Models::ForeverOrder] Modifying order #{order_id}")
|
|
295
|
-
|
|
298
|
+
full_params = snake_case(new_params)
|
|
299
|
+
config = DhanHQ.configuration
|
|
300
|
+
full_params[:dhan_client_id] ||= config.client_id if config&.client_id
|
|
301
|
+
full_params[:order_id] = order_id
|
|
302
|
+
validate_params!(full_params, DhanHQ::Contracts::ForeverOrderModifyContract)
|
|
303
|
+
formatted = camelize_keys(full_params)
|
|
304
|
+
response = self.class.resource.update(order_id, formatted)
|
|
296
305
|
ctx = "[DhanHQ::Models::ForeverOrder] Modification"
|
|
297
306
|
success = handle_api_response(response, success_key: "orderId", context: ctx)
|
|
298
307
|
return self.class.find(order_id) if success
|
|
@@ -56,11 +56,11 @@ module DhanHQ
|
|
|
56
56
|
# @param params [Hash{Symbol => String, Integer, Boolean}] Request parameters
|
|
57
57
|
# @option params [String] :security_id (required) Exchange standard ID for each scrip
|
|
58
58
|
# @option params [String] :exchange_segment (required) Exchange and segment for which data is to be fetched.
|
|
59
|
-
# Valid values: See {DhanHQ::Constants::
|
|
59
|
+
# Valid values: See {DhanHQ::Constants::CHART_EXCHANGE_SEGMENTS}
|
|
60
60
|
# @option params [String] :instrument (required) Instrument type of the scrip.
|
|
61
61
|
# Valid values: See {DhanHQ::Constants::INSTRUMENTS}
|
|
62
62
|
# @option params [Integer] :expiry_code (optional) Expiry of the instruments in case of derivatives.
|
|
63
|
-
# Valid values: 0, 1, 2
|
|
63
|
+
# Valid values: See {DhanHQ::Constants::ExpiryCode::ALL} (0, 1, 2)
|
|
64
64
|
# @option params [Boolean] :oi (optional) Include Open Interest data for Futures & Options.
|
|
65
65
|
# Default: false
|
|
66
66
|
# @option params [String] :from_date (required) Start date of the desired range in YYYY-MM-DD format
|
|
@@ -100,8 +100,9 @@ module DhanHQ
|
|
|
100
100
|
#
|
|
101
101
|
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
102
102
|
def daily(params)
|
|
103
|
-
validate_params!(params, DhanHQ::Contracts::HistoricalDataContract)
|
|
104
|
-
resource.daily(
|
|
103
|
+
validated_params = validate_params!(params, DhanHQ::Contracts::HistoricalDataContract)
|
|
104
|
+
response = resource.daily(validated_params)
|
|
105
|
+
normalize(response)
|
|
105
106
|
end
|
|
106
107
|
|
|
107
108
|
##
|
|
@@ -177,8 +178,41 @@ module DhanHQ
|
|
|
177
178
|
# make multiple requests or store data locally for analysis.
|
|
178
179
|
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
179
180
|
def intraday(params)
|
|
180
|
-
validate_params!(params, DhanHQ::Contracts::
|
|
181
|
-
resource.intraday(
|
|
181
|
+
validated_params = validate_params!(params, DhanHQ::Contracts::IntradayHistoricalDataContract)
|
|
182
|
+
response = resource.intraday(validated_params)
|
|
183
|
+
normalize(response)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
# Normalizes the columnar API response into an array of candle hashes.
|
|
189
|
+
#
|
|
190
|
+
# @param response [Hash] The raw API response
|
|
191
|
+
# @return [Array<Hash>, Hash] Normalized array of candles, or raw response if structure unexpected
|
|
192
|
+
def normalize(response)
|
|
193
|
+
# Use symbols or strings depending on HashWithIndifferentAccess behavior
|
|
194
|
+
close = response[:close] || response["close"]
|
|
195
|
+
return response unless response.is_a?(Hash) && close.is_a?(Array)
|
|
196
|
+
|
|
197
|
+
ts = response[:timestamp] || response["timestamp"]
|
|
198
|
+
open = response[:open] || response["open"]
|
|
199
|
+
high = response[:high] || response["high"]
|
|
200
|
+
low = response[:low] || response["low"]
|
|
201
|
+
volume = response[:volume] || response["volume"]
|
|
202
|
+
oi = response[:open_interest] || response["open_interest"]
|
|
203
|
+
|
|
204
|
+
(0...close.size).map do |i|
|
|
205
|
+
candle = {
|
|
206
|
+
timestamp: ts[i].is_a?(Numeric) ? Time.at(ts[i]) : ts[i],
|
|
207
|
+
open: open[i],
|
|
208
|
+
high: high[i],
|
|
209
|
+
low: low[i],
|
|
210
|
+
close: close[i],
|
|
211
|
+
volume: volume[i]
|
|
212
|
+
}
|
|
213
|
+
candle[:open_interest] = oi[i] if oi && oi[i]
|
|
214
|
+
candle
|
|
215
|
+
end
|
|
182
216
|
end
|
|
183
217
|
end
|
|
184
218
|
end
|
|
@@ -161,7 +161,8 @@ module DhanHQ
|
|
|
161
161
|
return seg if %w[IDX_I NSE_FNO BSE_FNO MCX_FO].include?(seg)
|
|
162
162
|
|
|
163
163
|
# Index detection by instrument kind or segment
|
|
164
|
-
|
|
164
|
+
idx_seg = DhanHQ::Constants::ExchangeSegment::IDX_I
|
|
165
|
+
return idx_seg if ins == DhanHQ::Constants::InstrumentType::INDEX || seg == idx_seg
|
|
165
166
|
|
|
166
167
|
# Map equities/stock-related segments to respective FNO
|
|
167
168
|
return DhanHQ::Constants::ExchangeSegment::NSE_FNO if seg.start_with?("NSE")
|
data/lib/DhanHQ/models/margin.rb
CHANGED
|
@@ -48,7 +48,9 @@ module DhanHQ
|
|
|
48
48
|
HTTP_PATH = "/v2/margincalculator"
|
|
49
49
|
|
|
50
50
|
attributes :total_margin, :span_margin, :exposure_margin, :available_balance,
|
|
51
|
-
:variable_margin, :insufficient_balance, :brokerage, :leverage
|
|
51
|
+
:variable_margin, :insufficient_balance, :brokerage, :leverage,
|
|
52
|
+
:client_id, :equity_margin, :fo_margin, :commodity_margin,
|
|
53
|
+
:currency, :hedge_benefit, :exposure, :commodity
|
|
52
54
|
|
|
53
55
|
class << self
|
|
54
56
|
##
|
|
@@ -70,28 +72,18 @@ module DhanHQ
|
|
|
70
72
|
# @option params [String] :dhan_client_id (required) User-specific identification generated by Dhan.
|
|
71
73
|
# Must be explicitly provided in the params hash
|
|
72
74
|
# @option params [String] :exchange_segment (required) Exchange and segment identifier.
|
|
73
|
-
# Valid values:
|
|
75
|
+
# Valid values: See {DhanHQ::Constants::MARGIN_CALCULATOR_SEGMENTS} (NSE_EQ, NSE_FNO, BSE_EQ, BSE_FNO, MCX_COMM)
|
|
74
76
|
# @option params [String] :transaction_type (required) The trading side of transaction.
|
|
75
77
|
# Valid values: "BUY", "SELL"
|
|
76
78
|
# @option params [Integer] :quantity (required) Number of shares for the order. Must be greater than 0
|
|
77
79
|
# @option params [String] :product_type (required) Product type.
|
|
78
|
-
# Valid values:
|
|
80
|
+
# Valid values: See {DhanHQ::Constants::MARGIN_PRODUCT_TYPES} (CNC, INTRADAY, MARGIN, MTF)
|
|
79
81
|
# @option params [String] :security_id (required) Exchange standard ID for each scrip
|
|
80
82
|
# @option params [Float] :price (required) Price at which order is placed. Must be greater than 0
|
|
81
83
|
# @option params [Float] :trigger_price (conditionally required) Price at which the order is triggered.
|
|
82
84
|
# Required for STOP_LOSS and STOP_LOSS_MARKET order types
|
|
83
85
|
#
|
|
84
86
|
# @return [Margin] Margin object containing margin calculation results.
|
|
85
|
-
# Response structure (keys normalized to snake_case):
|
|
86
|
-
# - **:total_margin** [Float] Total margin required for placing the order successfully
|
|
87
|
-
# - **:span_margin** [Float] SPAN margin required
|
|
88
|
-
# - **:exposure_margin** [Float] Exposure margin required
|
|
89
|
-
# - **:available_balance** [Float] Available amount in trading account
|
|
90
|
-
# - **:variable_margin** [Float] VAR or Variable margin required
|
|
91
|
-
# - **:insufficient_balance** [Float] Insufficient amount in trading account
|
|
92
|
-
# (Available Balance - Total Margin). Negative or zero indicates sufficient margin
|
|
93
|
-
# - **:brokerage** [Float] Brokerage charges for executing the order
|
|
94
|
-
# - **:leverage** [String] Margin leverage provided for the order as per product type
|
|
95
87
|
#
|
|
96
88
|
# @example Calculate margin for CNC equity order
|
|
97
89
|
# margin = DhanHQ::Models::Margin.calculate(
|
|
@@ -106,31 +98,6 @@ module DhanHQ
|
|
|
106
98
|
# puts "Total Margin: ₹#{margin.total_margin}"
|
|
107
99
|
# puts "Brokerage: ₹#{margin.brokerage}"
|
|
108
100
|
#
|
|
109
|
-
# @example Calculate margin for intraday order
|
|
110
|
-
# margin = DhanHQ::Models::Margin.calculate(
|
|
111
|
-
# dhan_client_id: "1000000132",
|
|
112
|
-
# exchange_segment: "NSE_EQ",
|
|
113
|
-
# transaction_type: "SELL",
|
|
114
|
-
# quantity: 10,
|
|
115
|
-
# product_type: "INTRADAY",
|
|
116
|
-
# security_id: "1333",
|
|
117
|
-
# price: 1500.0
|
|
118
|
-
# )
|
|
119
|
-
# puts "Leverage: #{margin.leverage}x"
|
|
120
|
-
# puts "SPAN Margin: ₹#{margin.span_margin}"
|
|
121
|
-
#
|
|
122
|
-
# @example Calculate margin for stop-loss order
|
|
123
|
-
# margin = DhanHQ::Models::Margin.calculate(
|
|
124
|
-
# dhan_client_id: "1000000132",
|
|
125
|
-
# exchange_segment: "NSE_EQ",
|
|
126
|
-
# transaction_type: "BUY",
|
|
127
|
-
# quantity: 5,
|
|
128
|
-
# product_type: "INTRADAY",
|
|
129
|
-
# security_id: "1333",
|
|
130
|
-
# price: 1428.0,
|
|
131
|
-
# trigger_price: 1427.0
|
|
132
|
-
# )
|
|
133
|
-
#
|
|
134
101
|
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
135
102
|
def calculate(params)
|
|
136
103
|
formatted_params = camelize_keys(params)
|
|
@@ -148,45 +115,68 @@ module DhanHQ
|
|
|
148
115
|
#
|
|
149
116
|
# @param params [Hash{Symbol => Object}] Request parameters
|
|
150
117
|
# @option params [Boolean] :include_position Whether to include existing positions
|
|
151
|
-
# @option params [Boolean] :
|
|
118
|
+
# @option params [Boolean] :include_orders Whether to include existing orders
|
|
152
119
|
# @option params [String] :dhan_client_id User-specific identification
|
|
153
|
-
# @option params [Array<Hash>] :
|
|
154
|
-
# - :exchange_segment [String]
|
|
155
|
-
# - :transaction_type [String]
|
|
120
|
+
# @option params [Array<Hash>] :scripts Array of instrument margin params, each with:
|
|
121
|
+
# - :exchange_segment [String] See {DhanHQ::Constants::MARGIN_CALCULATOR_SEGMENTS}
|
|
122
|
+
# - :transaction_type [String] BUY or SELL
|
|
156
123
|
# - :quantity [Integer]
|
|
157
|
-
# - :product_type [String]
|
|
124
|
+
# - :product_type [String] See {DhanHQ::Constants::MARGIN_PRODUCT_TYPES}
|
|
158
125
|
# - :security_id [String]
|
|
159
|
-
# - :price [Float]
|
|
126
|
+
# - :price [Float] (required)
|
|
160
127
|
# - :trigger_price [Float] (optional)
|
|
161
128
|
#
|
|
162
|
-
# @return [
|
|
163
|
-
# - **:total_margin** [String] Total margin required
|
|
164
|
-
# - **:span_margin** [String] SPAN margin
|
|
165
|
-
# - **:exposure_margin** [String] Exposure margin
|
|
166
|
-
# - **:equity_margin** [String] Equity margin
|
|
167
|
-
# - **:fo_margin** [String] F&O margin
|
|
168
|
-
# - **:commodity_margin** [String] Commodity margin
|
|
169
|
-
# - **:currency** [String] Currency (e.g., "INR")
|
|
170
|
-
# - **:hedge_benefit** [String] Hedge benefit amount
|
|
129
|
+
# @return [Margin] Margin object containing combined results.
|
|
171
130
|
#
|
|
172
131
|
# @example Calculate margin for multiple scripts
|
|
173
|
-
#
|
|
132
|
+
# margin = DhanHQ::Models::Margin.calculate_multi(
|
|
174
133
|
# include_position: true,
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
# scrip_list: [
|
|
134
|
+
# include_orders: true,
|
|
135
|
+
# scripts: [
|
|
178
136
|
# { exchange_segment: "NSE_EQ", transaction_type: "BUY",
|
|
179
137
|
# quantity: 100, product_type: "CNC", security_id: "1333", price: 1428.0 },
|
|
180
|
-
# { exchange_segment: "
|
|
181
|
-
# quantity: 50, product_type: "INTRADAY", security_id: "
|
|
138
|
+
# { exchange_segment: "NSE_EQ", transaction_type: "SELL",
|
|
139
|
+
# quantity: 50, product_type: "INTRADAY", security_id: "11536", price: 3000.0 }
|
|
182
140
|
# ]
|
|
183
141
|
# )
|
|
184
|
-
# puts "Total margin: #{
|
|
185
|
-
# puts "Hedge benefit: #{result[:hedge_benefit]}"
|
|
142
|
+
# puts "Total margin: #{margin.total_margin}"
|
|
186
143
|
#
|
|
187
144
|
def calculate_multi(params)
|
|
188
|
-
|
|
189
|
-
|
|
145
|
+
# Map scripts to scrip_list and include_orders to include_order if provided
|
|
146
|
+
params[:scrip_list] ||= params[:scripts] if params.key?(:scripts)
|
|
147
|
+
params[:include_order] ||= params[:include_orders] if params.key?(:include_orders)
|
|
148
|
+
params[:dhan_client_id] ||= DhanHQ.configuration.client_id
|
|
149
|
+
|
|
150
|
+
# Filter only keys supported by the API
|
|
151
|
+
filtered_params = {
|
|
152
|
+
includePosition: params[:include_position],
|
|
153
|
+
includeOrder: params[:include_order],
|
|
154
|
+
dhanClientId: params[:dhan_client_id],
|
|
155
|
+
scripList: params[:scrip_list]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if filtered_params[:scripList].is_a?(Array)
|
|
159
|
+
filtered_params[:scripList] = filtered_params[:scripList].map do |scrip|
|
|
160
|
+
if scrip.is_a?(Hash)
|
|
161
|
+
{
|
|
162
|
+
exchangeSegment: scrip[:exchange_segment] || scrip[:exchangeSegment],
|
|
163
|
+
transactionType: scrip[:transaction_type] || scrip[:transactionType],
|
|
164
|
+
quantity: scrip[:quantity],
|
|
165
|
+
productType: scrip[:product_type] || scrip[:productType],
|
|
166
|
+
orderType: scrip[:order_type] || scrip[:orderType],
|
|
167
|
+
securityId: scrip[:security_id] || scrip[:securityId],
|
|
168
|
+
price: scrip[:price],
|
|
169
|
+
triggerPrice: scrip[:trigger_price] || scrip[:triggerPrice]
|
|
170
|
+
}.compact
|
|
171
|
+
else
|
|
172
|
+
scrip
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
validate_params!(filtered_params, DhanHQ::Contracts::MultiScripMarginCalcRequestContract)
|
|
178
|
+
response = resource.calculate_multi(filtered_params)
|
|
179
|
+
new(response, skip_validation: true)
|
|
190
180
|
end
|
|
191
181
|
end
|
|
192
182
|
|
|
@@ -195,33 +185,23 @@ module DhanHQ
|
|
|
195
185
|
#
|
|
196
186
|
# Useful for serialization, logging, or passing margin data to other methods.
|
|
197
187
|
#
|
|
198
|
-
# @return [Hash{Symbol => Float, String}] Hash representation of the Margin model
|
|
199
|
-
# - **:total_margin** [Float] Total margin required
|
|
200
|
-
# - **:span_margin** [Float] SPAN margin
|
|
201
|
-
# - **:exposure_margin** [Float] Exposure margin
|
|
202
|
-
# - **:available_balance** [Float] Available balance
|
|
203
|
-
# - **:variable_margin** [Float] Variable margin
|
|
204
|
-
# - **:insufficient_balance** [Float] Insufficient balance amount
|
|
205
|
-
# - **:brokerage** [Float] Brokerage charges
|
|
206
|
-
# - **:leverage** [String] Leverage as string
|
|
207
|
-
#
|
|
208
|
-
# @example Convert margin to hash
|
|
209
|
-
# margin = DhanHQ::Models::Margin.calculate(params)
|
|
210
|
-
# margin_hash = margin.to_h
|
|
211
|
-
# puts margin_hash[:total_margin] # => 2800.00
|
|
212
|
-
# puts margin_hash[:leverage] # => "4.00"
|
|
213
|
-
#
|
|
188
|
+
# @return [Hash{Symbol => Float, String}] Hash representation of the Margin model.
|
|
214
189
|
def to_h
|
|
215
190
|
{
|
|
216
191
|
total_margin: total_margin,
|
|
217
192
|
span_margin: span_margin,
|
|
218
|
-
exposure_margin: exposure_margin,
|
|
193
|
+
exposure_margin: exposure_margin || exposure,
|
|
219
194
|
available_balance: available_balance,
|
|
220
195
|
variable_margin: variable_margin,
|
|
221
196
|
insufficient_balance: insufficient_balance,
|
|
222
197
|
brokerage: brokerage,
|
|
223
|
-
leverage: leverage
|
|
224
|
-
|
|
198
|
+
leverage: leverage,
|
|
199
|
+
equity_margin: equity_margin,
|
|
200
|
+
fo_margin: fo_margin,
|
|
201
|
+
commodity_margin: commodity_margin || commodity,
|
|
202
|
+
currency: currency,
|
|
203
|
+
hedge_benefit: hedge_benefit
|
|
204
|
+
}.compact
|
|
225
205
|
end
|
|
226
206
|
end
|
|
227
207
|
end
|
|
@@ -43,6 +43,14 @@ module DhanHQ
|
|
|
43
43
|
#
|
|
44
44
|
class MarketFeed < BaseModel
|
|
45
45
|
class << self
|
|
46
|
+
##
|
|
47
|
+
# Returns the validation contract for MarketFeed requests.
|
|
48
|
+
#
|
|
49
|
+
# @return [Class] The MarketFeedContract class
|
|
50
|
+
def validation_contract
|
|
51
|
+
DhanHQ::Contracts::MarketFeedContract
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
##
|
|
47
55
|
# Provides a shared instance of the MarketFeed resource.
|
|
48
56
|
#
|
|
@@ -84,7 +92,8 @@ module DhanHQ
|
|
|
84
92
|
# puts "Last Price: ₹#{data[:last_price]}"
|
|
85
93
|
#
|
|
86
94
|
def ltp(params)
|
|
87
|
-
|
|
95
|
+
validated_params = validate_params!(params, validation_contract)
|
|
96
|
+
resource.ltp(validated_params)
|
|
88
97
|
end
|
|
89
98
|
|
|
90
99
|
##
|
|
@@ -127,7 +136,8 @@ module DhanHQ
|
|
|
127
136
|
# puts "LTP: ₹#{tcs_data[:last_price]}"
|
|
128
137
|
#
|
|
129
138
|
def ohlc(params)
|
|
130
|
-
|
|
139
|
+
validated_params = validate_params!(params, validation_contract)
|
|
140
|
+
resource.ohlc(validated_params)
|
|
131
141
|
end
|
|
132
142
|
|
|
133
143
|
##
|
|
@@ -197,7 +207,8 @@ module DhanHQ
|
|
|
197
207
|
# puts "Best Buy Quantity: #{buy_depth[0][:quantity]}"
|
|
198
208
|
#
|
|
199
209
|
def quote(params)
|
|
200
|
-
|
|
210
|
+
validated_params = validate_params!(params, validation_contract)
|
|
211
|
+
resource.quote(validated_params)
|
|
201
212
|
end
|
|
202
213
|
end
|
|
203
214
|
end
|