DhanHQ 2.5.0 → 2.6.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -1
- data/CHANGELOG.md +78 -6
- data/GUIDE.md +57 -39
- data/README.md +24 -23
- data/docs/API_DOCS_GAPS.md +128 -0
- data/docs/API_VERIFICATION.md +10 -11
- data/docs/ARCHIVE_README.md +16 -16
- data/docs/AUTHENTICATION.md +1 -1
- data/docs/CONSTANTS_REFERENCE.md +477 -0
- data/docs/DATA_API_PARAMETERS.md +7 -7
- data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
- data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
- data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
- data/docs/TESTING_GUIDE.md +84 -82
- data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
- data/docs/WEBSOCKET_PROTOCOL.md +2 -2
- data/lib/DhanHQ/constants.rb +456 -151
- data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
- data/lib/DhanHQ/contracts/base_contract.rb +22 -0
- data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
- data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
- data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
- data/lib/DhanHQ/contracts/order_contract.rb +171 -39
- data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
- data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
- data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
- data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
- data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
- data/lib/DhanHQ/core/base_model.rb +13 -4
- data/lib/DhanHQ/helpers/response_helper.rb +2 -2
- data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
- data/lib/DhanHQ/models/alert_order.rb +7 -11
- data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
- data/lib/DhanHQ/models/edis.rb +0 -9
- data/lib/DhanHQ/models/expired_options_data.rb +6 -12
- data/lib/DhanHQ/models/forever_order.rb +8 -5
- data/lib/DhanHQ/models/historical_data.rb +0 -8
- data/lib/DhanHQ/models/instrument.rb +1 -7
- data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
- data/lib/DhanHQ/models/kill_switch.rb +1 -11
- data/lib/DhanHQ/models/margin.rb +2 -2
- data/lib/DhanHQ/models/order.rb +107 -126
- data/lib/DhanHQ/models/order_update.rb +7 -13
- data/lib/DhanHQ/models/pnl_exit.rb +1 -9
- data/lib/DhanHQ/models/position.rb +1 -1
- data/lib/DhanHQ/models/postback.rb +4 -13
- data/lib/DhanHQ/models/profile.rb +0 -10
- data/lib/DhanHQ/models/super_order.rb +13 -3
- data/lib/DhanHQ/models/trade.rb +11 -23
- data/lib/DhanHQ/resources/ip_setup.rb +16 -5
- data/lib/DhanHQ/resources/kill_switch.rb +9 -7
- data/lib/DhanHQ/resources/orders.rb +41 -41
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
- data/lib/DhanHQ/ws/orders/client.rb +6 -6
- data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
- data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
- data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
- metadata +20 -23
- data/TODO-1.md +0 -14
- data/TODO.md +0 -127
- data/app/services/live/order_update_guard_support.rb +0 -75
- data/app/services/live/order_update_hub.rb +0 -76
- data/app/services/live/order_update_persistence_support.rb +0 -68
- data/docs/PR_2.2.0.md +0 -48
- data/examples/comprehensive_websocket_examples.rb +0 -148
- data/examples/instrument_finder_test.rb +0 -195
- data/examples/live_order_updates.rb +0 -118
- data/examples/market_depth_example.rb +0 -144
- data/examples/market_feed_example.rb +0 -81
- data/examples/order_update_example.rb +0 -105
- data/examples/trading_fields_example.rb +0 -215
- /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
- /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
data/lib/DhanHQ/models/order.rb
CHANGED
|
@@ -46,6 +46,8 @@ module DhanHQ
|
|
|
46
46
|
# puts "Pending orders: #{pending_orders.count}"
|
|
47
47
|
#
|
|
48
48
|
class Order < BaseModel
|
|
49
|
+
include Concerns::ApiResponseHandler
|
|
50
|
+
|
|
49
51
|
# Attributes eligible for modification requests.
|
|
50
52
|
MODIFIABLE_FIELDS = %i[
|
|
51
53
|
dhan_client_id
|
|
@@ -57,10 +59,10 @@ module DhanHQ
|
|
|
57
59
|
disclosed_quantity
|
|
58
60
|
validity
|
|
59
61
|
leg_name
|
|
62
|
+
bo_profit_value
|
|
63
|
+
bo_stop_loss_value
|
|
60
64
|
].freeze
|
|
61
65
|
|
|
62
|
-
attr_reader :order_id, :order_status
|
|
63
|
-
|
|
64
66
|
# Define attributes that are part of an order
|
|
65
67
|
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
|
66
68
|
:transaction_type, :exchange_segment, :product_type, :order_type,
|
|
@@ -168,9 +170,10 @@ module DhanHQ
|
|
|
168
170
|
#
|
|
169
171
|
def find(order_id)
|
|
170
172
|
response = resource.find(order_id)
|
|
171
|
-
|
|
173
|
+
is_array = response.is_a?(Array)
|
|
174
|
+
return nil unless response.is_a?(Hash) || (is_array && response.any?)
|
|
172
175
|
|
|
173
|
-
order_data =
|
|
176
|
+
order_data = is_array ? response.first : response
|
|
174
177
|
new(order_data, skip_validation: true)
|
|
175
178
|
end
|
|
176
179
|
|
|
@@ -286,15 +289,19 @@ module DhanHQ
|
|
|
286
289
|
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
287
290
|
def place(params)
|
|
288
291
|
normalized_params = snake_case(params)
|
|
292
|
+
config = DhanHQ.configuration
|
|
289
293
|
# Auto-inject dhan_client_id from configuration if not provided
|
|
290
|
-
normalized_params[:dhan_client_id] ||=
|
|
294
|
+
normalized_params[:dhan_client_id] ||= config.client_id if config&.client_id
|
|
291
295
|
validate_params!(normalized_params, DhanHQ::Contracts::PlaceOrderContract)
|
|
292
296
|
|
|
293
297
|
response = resource.create(camelize_keys(normalized_params))
|
|
294
|
-
return nil unless response.is_a?(Hash)
|
|
298
|
+
return nil unless response.is_a?(Hash)
|
|
299
|
+
|
|
300
|
+
order_id = response["orderId"]
|
|
301
|
+
return nil unless order_id
|
|
295
302
|
|
|
296
303
|
# Fetch the complete order details
|
|
297
|
-
find(
|
|
304
|
+
find(order_id)
|
|
298
305
|
end
|
|
299
306
|
|
|
300
307
|
##
|
|
@@ -366,25 +373,12 @@ module DhanHQ
|
|
|
366
373
|
def modify(new_params)
|
|
367
374
|
raise "Order ID is required to modify an order" unless id
|
|
368
375
|
|
|
369
|
-
|
|
370
|
-
# This maintains backward compatibility - API will return appropriate error
|
|
371
|
-
if order_status && %w[TRADED CANCELLED EXPIRED CLOSED].include?(order_status)
|
|
372
|
-
DhanHQ.logger&.warn("[DhanHQ::Models::Order] Attempting to modify order #{id} in #{order_status} state - API will reject")
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
base_payload = attributes.merge(new_params)
|
|
376
|
-
normalized_payload = snake_case(base_payload).merge(order_id: id)
|
|
377
|
-
filtered_payload = normalized_payload.each_with_object({}) do |(key, value), memo|
|
|
378
|
-
symbolized_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
|
379
|
-
memo[symbolized_key] = value if MODIFIABLE_FIELDS.include?(symbolized_key)
|
|
380
|
-
end
|
|
381
|
-
filtered_payload[:order_id] ||= id
|
|
382
|
-
filtered_payload[:dhan_client_id] ||= attributes[:dhan_client_id] || DhanHQ.configuration&.client_id
|
|
376
|
+
warn_invalid_state if order_status_invalid_for_modification?
|
|
383
377
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
validate_params!(formatted_payload, DhanHQ::Contracts::ModifyOrderContract)
|
|
378
|
+
filtered_payload = prepare_modify_payload(new_params)
|
|
379
|
+
validate_params!(filtered_payload, DhanHQ::Contracts::ModifyOrderContract)
|
|
387
380
|
|
|
381
|
+
formatted_payload = camelize_keys(filtered_payload)
|
|
388
382
|
response = self.class.resource.update(id, formatted_payload)
|
|
389
383
|
response = response.with_indifferent_access if response.respond_to?(:with_indifferent_access)
|
|
390
384
|
|
|
@@ -415,8 +409,15 @@ module DhanHQ
|
|
|
415
409
|
def cancel
|
|
416
410
|
raise "Order ID is required to cancel an order" unless id
|
|
417
411
|
|
|
412
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Cancelling order #{id}")
|
|
418
413
|
response = self.class.resource.cancel(id)
|
|
419
|
-
response["orderStatus"] ==
|
|
414
|
+
if response["orderStatus"] == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
415
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Order #{id} cancelled successfully")
|
|
416
|
+
true
|
|
417
|
+
else
|
|
418
|
+
DhanHQ.logger&.error("[DhanHQ::Models::Order] Cancel failed for order #{id}: #{response.inspect}")
|
|
419
|
+
false
|
|
420
|
+
end
|
|
420
421
|
end
|
|
421
422
|
|
|
422
423
|
##
|
|
@@ -453,6 +454,44 @@ module DhanHQ
|
|
|
453
454
|
order_id.nil? || order_id.to_s.empty?
|
|
454
455
|
end
|
|
455
456
|
|
|
457
|
+
def destroy
|
|
458
|
+
return false if new_record?
|
|
459
|
+
|
|
460
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Destroying order #{id}")
|
|
461
|
+
response = self.class.resource.delete(id)
|
|
462
|
+
if success_response?(response) && response["orderStatus"] == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
463
|
+
@attributes[:order_status] = DhanHQ::Constants::OrderStatus::CANCELLED
|
|
464
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Order #{id} destroyed successfully")
|
|
465
|
+
true
|
|
466
|
+
else
|
|
467
|
+
DhanHQ.logger&.error("[DhanHQ::Models::Order] Destroy failed for order #{id}")
|
|
468
|
+
false
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
alias delete destroy
|
|
472
|
+
|
|
473
|
+
def slice_order(params)
|
|
474
|
+
raise "Order ID is required to slice an order" unless id
|
|
475
|
+
|
|
476
|
+
normalized = snake_case(params)
|
|
477
|
+
normalized[:dhan_client_id] ||= DhanHQ.configuration&.client_id
|
|
478
|
+
base_payload = normalized.merge(order_id: id)
|
|
479
|
+
formatted_payload = camelize_keys(base_payload)
|
|
480
|
+
|
|
481
|
+
validate_params!(formatted_payload, DhanHQ::Contracts::SliceOrderContract)
|
|
482
|
+
|
|
483
|
+
self.class.resource.slicing(formatted_payload)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def validation_contract
|
|
487
|
+
order_id_val = @attributes[:order_id] || @attributes[:orderId]
|
|
488
|
+
if order_id_val.nil? || order_id_val.to_s.empty?
|
|
489
|
+
DhanHQ::Contracts::PlaceOrderContract.new
|
|
490
|
+
else
|
|
491
|
+
DhanHQ::Contracts::ModifyOrderContract.new
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
456
495
|
##
|
|
457
496
|
# Returns the order ID used for resource calls.
|
|
458
497
|
#
|
|
@@ -495,40 +534,53 @@ module DhanHQ
|
|
|
495
534
|
return false unless valid?
|
|
496
535
|
|
|
497
536
|
if new_record?
|
|
498
|
-
|
|
499
|
-
DhanHQ.logger&.info("[DhanHQ::Models::Order] Placing order: #{attributes.slice(:transaction_type,
|
|
500
|
-
:exchange_segment, :security_id, :quantity, :price).inspect}")
|
|
501
|
-
response = self.class.resource.create(to_request_params)
|
|
502
|
-
if success_response?(response) && response["orderId"]
|
|
503
|
-
@attributes.merge!(normalize_keys(response))
|
|
504
|
-
assign_attributes
|
|
505
|
-
DhanHQ.logger&.info("[DhanHQ::Models::Order] Order placed successfully: #{response["orderId"]}")
|
|
506
|
-
true
|
|
507
|
-
else
|
|
508
|
-
error_msg = response.is_a?(Hash) ? response[:errorMessage] || response[:message] || "Unknown error" : "Invalid response format"
|
|
509
|
-
DhanHQ.logger&.error("[DhanHQ::Models::Order] Order placement failed: #{error_msg}")
|
|
510
|
-
@errors = response if response.is_a?(Hash)
|
|
511
|
-
false
|
|
512
|
-
end
|
|
537
|
+
save_new_order
|
|
513
538
|
else
|
|
514
|
-
|
|
515
|
-
DhanHQ.logger&.info("[DhanHQ::Models::Order] Modifying order #{id}: #{attributes.slice(:price, :quantity,
|
|
516
|
-
:order_type).inspect}")
|
|
517
|
-
response = self.class.resource.update(id, to_request_params)
|
|
518
|
-
if success_response?(response) && response["orderStatus"]
|
|
519
|
-
@attributes.merge!(normalize_keys(response))
|
|
520
|
-
assign_attributes
|
|
521
|
-
DhanHQ.logger&.info("[DhanHQ::Models::Order] Order modified successfully: #{id}")
|
|
522
|
-
true
|
|
523
|
-
else
|
|
524
|
-
error_msg = response.is_a?(Hash) ? response[:errorMessage] || response[:message] || "Unknown error" : "Invalid response format"
|
|
525
|
-
DhanHQ.logger&.error("[DhanHQ::Models::Order] Order modification failed for #{id}: #{error_msg}")
|
|
526
|
-
@errors = response if response.is_a?(Hash)
|
|
527
|
-
false
|
|
528
|
-
end
|
|
539
|
+
modify_existing_order
|
|
529
540
|
end
|
|
530
541
|
end
|
|
531
542
|
|
|
543
|
+
private
|
|
544
|
+
|
|
545
|
+
def save_new_order
|
|
546
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Placing order: #{attributes.slice(:transaction_type, :exchange_segment, :security_id, :quantity, :price).inspect}")
|
|
547
|
+
response = self.class.resource.create(to_request_params)
|
|
548
|
+
handle_api_response(response, success_key: "orderId", context: "[DhanHQ::Models::Order] Order placement")
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def modify_existing_order
|
|
552
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Modifying order #{id}: #{attributes.slice(:price, :quantity, :order_type).inspect}")
|
|
553
|
+
response = self.class.resource.update(id, to_request_params)
|
|
554
|
+
handle_api_response(response, success_key: "orderStatus", context: "[DhanHQ::Models::Order] Order modification")
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def order_status_invalid_for_modification?
|
|
558
|
+
order_status && %w[TRADED CANCELLED EXPIRED CLOSED].include?(order_status)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def warn_invalid_state
|
|
562
|
+
DhanHQ.logger&.warn("[DhanHQ::Models::Order] Attempting to modify order #{id} in #{order_status} state - API will reject")
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def prepare_modify_payload(new_params)
|
|
566
|
+
base_payload = attributes.merge(new_params)
|
|
567
|
+
normalized_payload = snake_case(base_payload).merge(order_id: id)
|
|
568
|
+
|
|
569
|
+
filtered_payload = normalized_payload.each_with_object({}) do |(key, value), memo|
|
|
570
|
+
symbolized_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
|
571
|
+
memo[symbolized_key] = value if MODIFIABLE_FIELDS.include?(symbolized_key)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
filtered_payload[:order_id] ||= id
|
|
575
|
+
filtered_payload[:dhan_client_id] ||= attributes[:dhan_client_id] || DhanHQ.configuration&.client_id
|
|
576
|
+
|
|
577
|
+
# Don't send trigger_price when it's 0 for non–stop-loss orders (API default; avoids validation noise).
|
|
578
|
+
order_type = filtered_payload[:order_type].to_s
|
|
579
|
+
filtered_payload.delete(:trigger_price) if !%w[STOP_LOSS STOP_LOSS_MARKET].include?(order_type) && filtered_payload[:trigger_price].to_f.zero?
|
|
580
|
+
|
|
581
|
+
filtered_payload.compact
|
|
582
|
+
end
|
|
583
|
+
|
|
532
584
|
##
|
|
533
585
|
# Cancels (destroys) the order.
|
|
534
586
|
#
|
|
@@ -545,77 +597,6 @@ module DhanHQ
|
|
|
545
597
|
# end
|
|
546
598
|
#
|
|
547
599
|
# @note This method does nothing for new (unsaved) orders.
|
|
548
|
-
def destroy
|
|
549
|
-
return false if new_record?
|
|
550
|
-
|
|
551
|
-
response = self.class.resource.delete(id)
|
|
552
|
-
if success_response?(response) && response["orderStatus"] == "CANCELLED"
|
|
553
|
-
@attributes[:order_status] = "CANCELLED"
|
|
554
|
-
true
|
|
555
|
-
else
|
|
556
|
-
false
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
alias delete destroy
|
|
560
|
-
|
|
561
|
-
##
|
|
562
|
-
# Slices an order into multiple legs to place orders over freeze limit quantity.
|
|
563
|
-
#
|
|
564
|
-
# This API helps you slice your order request into multiple orders to allow you
|
|
565
|
-
# to place over freeze limit quantity for F&O instruments. Returns an array of
|
|
566
|
-
# orders created from the slice operation.
|
|
567
|
-
#
|
|
568
|
-
# @param params [Hash{Symbol => String, Integer, Float, Boolean}] Order parameters for slicing.
|
|
569
|
-
# Same parameters as {place}, but quantity can exceed freeze limits as it will be
|
|
570
|
-
# automatically split into multiple orders.
|
|
571
|
-
#
|
|
572
|
-
# @return [Array<Hash>] Array of order objects created from the slice operation.
|
|
573
|
-
# Each hash contains:
|
|
574
|
-
# - **:order_id** [String] Order-specific identification generated by Dhan
|
|
575
|
-
# - **:order_status** [String] Order status. Valid values: "TRANSIT", "PENDING",
|
|
576
|
-
# "REJECTED", "CANCELLED", "TRADED", "EXPIRED", "CONFIRM"
|
|
577
|
-
#
|
|
578
|
-
# @example Slice a large order
|
|
579
|
-
# order = DhanHQ::Models::Order.find("112111182045")
|
|
580
|
-
# sliced_orders = order.slice_order(
|
|
581
|
-
# dhan_client_id: "1000000003",
|
|
582
|
-
# transaction_type: "BUY",
|
|
583
|
-
# exchange_segment: "NSE_FNO",
|
|
584
|
-
# product_type: "INTRADAY",
|
|
585
|
-
# order_type: "MARKET",
|
|
586
|
-
# validity: "DAY",
|
|
587
|
-
# security_id: "49081",
|
|
588
|
-
# quantity: 50000 # Will be split into multiple orders
|
|
589
|
-
# )
|
|
590
|
-
# puts "Created #{sliced_orders.count} orders"
|
|
591
|
-
#
|
|
592
|
-
# @raise [RuntimeError] If order ID is missing
|
|
593
|
-
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
594
|
-
def slice_order(params)
|
|
595
|
-
raise "Order ID is required to slice an order" unless id
|
|
596
|
-
|
|
597
|
-
base_payload = params.merge(order_id: id)
|
|
598
|
-
formatted_payload = camelize_keys(base_payload)
|
|
599
|
-
|
|
600
|
-
validate_params!(formatted_payload, DhanHQ::Contracts::SliceOrderContract)
|
|
601
|
-
|
|
602
|
-
self.class.resource.slicing(formatted_payload)
|
|
603
|
-
end
|
|
604
|
-
|
|
605
|
-
##
|
|
606
|
-
# Returns the appropriate validation contract based on order state.
|
|
607
|
-
#
|
|
608
|
-
# For new records, returns PlaceOrderContract. For existing records, returns
|
|
609
|
-
# ModifyOrderContract. This allows the same validation logic to be used for
|
|
610
|
-
# both creating and modifying orders.
|
|
611
|
-
#
|
|
612
|
-
# @return [DhanHQ::Contracts::PlaceOrderContract, DhanHQ::Contracts::ModifyOrderContract]
|
|
613
|
-
# The appropriate validation contract based on whether this is a new or existing order
|
|
614
|
-
#
|
|
615
|
-
# @api private
|
|
616
|
-
def validation_contract
|
|
617
|
-
new_record? ? DhanHQ::Contracts::PlaceOrderContract.new : DhanHQ::Contracts::ModifyOrderContract.new
|
|
618
|
-
end
|
|
619
600
|
end
|
|
620
601
|
end
|
|
621
602
|
end
|
|
@@ -31,12 +31,6 @@ module DhanHQ
|
|
|
31
31
|
new(data, skip_validation: true)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
##
|
|
35
|
-
# OrderUpdate objects are read-only, so no validation contract needed
|
|
36
|
-
def validation_contract
|
|
37
|
-
nil
|
|
38
|
-
end
|
|
39
|
-
|
|
40
34
|
##
|
|
41
35
|
# Helper methods for transaction type
|
|
42
36
|
def buy?
|
|
@@ -94,33 +88,33 @@ module DhanHQ
|
|
|
94
88
|
##
|
|
95
89
|
# Helper methods for order status
|
|
96
90
|
def transit?
|
|
97
|
-
status ==
|
|
91
|
+
status == DhanHQ::Constants::OrderStatus::TRANSIT
|
|
98
92
|
end
|
|
99
93
|
|
|
100
94
|
def pending?
|
|
101
|
-
status ==
|
|
95
|
+
status == DhanHQ::Constants::OrderStatus::PENDING
|
|
102
96
|
end
|
|
103
97
|
|
|
104
98
|
def rejected?
|
|
105
|
-
status ==
|
|
99
|
+
status == DhanHQ::Constants::OrderStatus::REJECTED
|
|
106
100
|
end
|
|
107
101
|
|
|
108
102
|
def cancelled?
|
|
109
|
-
status ==
|
|
103
|
+
status == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
110
104
|
end
|
|
111
105
|
|
|
112
106
|
def traded?
|
|
113
|
-
status ==
|
|
107
|
+
status == DhanHQ::Constants::OrderStatus::TRADED
|
|
114
108
|
end
|
|
115
109
|
|
|
116
110
|
def expired?
|
|
117
|
-
status ==
|
|
111
|
+
status == DhanHQ::Constants::OrderStatus::EXPIRED
|
|
118
112
|
end
|
|
119
113
|
|
|
120
114
|
##
|
|
121
115
|
# Helper methods for instrument type
|
|
122
116
|
def equity?
|
|
123
|
-
instrument ==
|
|
117
|
+
instrument == DhanHQ::Constants::InstrumentType::EQUITY
|
|
124
118
|
end
|
|
125
119
|
|
|
126
120
|
def derivative?
|
|
@@ -75,6 +75,7 @@ module DhanHQ
|
|
|
75
75
|
productType: product_type,
|
|
76
76
|
enableKillSwitch: enable_kill_switch
|
|
77
77
|
}
|
|
78
|
+
params[:dhanClientId] = DhanHQ.configuration.client_id if DhanHQ.configuration&.client_id.to_s != ""
|
|
78
79
|
resource.configure(params)
|
|
79
80
|
end
|
|
80
81
|
|
|
@@ -116,15 +117,6 @@ module DhanHQ
|
|
|
116
117
|
new(response, skip_validation: true)
|
|
117
118
|
end
|
|
118
119
|
end
|
|
119
|
-
|
|
120
|
-
##
|
|
121
|
-
# No validation contract needed — server-side validation handles it.
|
|
122
|
-
#
|
|
123
|
-
# @return [nil]
|
|
124
|
-
# @api private
|
|
125
|
-
def validation_contract
|
|
126
|
-
nil
|
|
127
|
-
end
|
|
128
120
|
end
|
|
129
121
|
end
|
|
130
122
|
end
|
|
@@ -139,7 +139,7 @@ module DhanHQ
|
|
|
139
139
|
# puts "Total open position value: ₹#{total_value}"
|
|
140
140
|
#
|
|
141
141
|
def active
|
|
142
|
-
all.reject { |position| position.position_type ==
|
|
142
|
+
all.reject { |position| position.position_type == DhanHQ::Constants::OrderStatus::CLOSED }
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
##
|
|
@@ -83,7 +83,7 @@ module DhanHQ
|
|
|
83
83
|
#
|
|
84
84
|
# @return [Boolean]
|
|
85
85
|
def traded?
|
|
86
|
-
order_status ==
|
|
86
|
+
order_status == DhanHQ::Constants::OrderStatus::TRADED
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
##
|
|
@@ -91,7 +91,7 @@ module DhanHQ
|
|
|
91
91
|
#
|
|
92
92
|
# @return [Boolean]
|
|
93
93
|
def rejected?
|
|
94
|
-
order_status ==
|
|
94
|
+
order_status == DhanHQ::Constants::OrderStatus::REJECTED
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
##
|
|
@@ -99,7 +99,7 @@ module DhanHQ
|
|
|
99
99
|
#
|
|
100
100
|
# @return [Boolean]
|
|
101
101
|
def pending?
|
|
102
|
-
order_status ==
|
|
102
|
+
order_status == DhanHQ::Constants::OrderStatus::PENDING
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
##
|
|
@@ -107,16 +107,7 @@ module DhanHQ
|
|
|
107
107
|
#
|
|
108
108
|
# @return [Boolean]
|
|
109
109
|
def cancelled?
|
|
110
|
-
order_status ==
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
##
|
|
114
|
-
# No validation contract — postback payloads are parsed as-is.
|
|
115
|
-
#
|
|
116
|
-
# @return [nil]
|
|
117
|
-
# @api private
|
|
118
|
-
def validation_contract
|
|
119
|
-
nil
|
|
110
|
+
order_status == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
120
111
|
end
|
|
121
112
|
end
|
|
122
113
|
end
|
|
@@ -125,16 +125,6 @@ module DhanHQ
|
|
|
125
125
|
|
|
126
126
|
##
|
|
127
127
|
# Profile responses are informational and not validated locally.
|
|
128
|
-
#
|
|
129
|
-
# Since profile data is read-only account metadata, no validation contract
|
|
130
|
-
# is needed. The API response is trusted as-is.
|
|
131
|
-
#
|
|
132
|
-
# @return [nil] Always returns nil as profiles are read-only and informational
|
|
133
|
-
#
|
|
134
|
-
# @api private
|
|
135
|
-
def validation_contract
|
|
136
|
-
nil
|
|
137
|
-
end
|
|
138
128
|
end
|
|
139
129
|
end
|
|
140
130
|
end
|
|
@@ -48,6 +48,8 @@ module DhanHQ
|
|
|
48
48
|
# order.cancel("STOP_LOSS_LEG")
|
|
49
49
|
#
|
|
50
50
|
class SuperOrder < BaseModel
|
|
51
|
+
include Concerns::ApiResponseHandler
|
|
52
|
+
|
|
51
53
|
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
|
52
54
|
:transaction_type, :exchange_segment, :product_type, :order_type,
|
|
53
55
|
:validity, :trading_symbol, :security_id, :quantity,
|
|
@@ -284,8 +286,12 @@ module DhanHQ
|
|
|
284
286
|
def modify(new_params)
|
|
285
287
|
raise "Order ID is required to modify a super order" unless id
|
|
286
288
|
|
|
289
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Modifying super order #{id}")
|
|
287
290
|
response = self.class.resource.update(id, new_params)
|
|
288
|
-
response["orderId"] == id
|
|
291
|
+
return false unless response.is_a?(Hash) && response["orderId"] == id
|
|
292
|
+
|
|
293
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Super order #{id} modified successfully")
|
|
294
|
+
true
|
|
289
295
|
end
|
|
290
296
|
|
|
291
297
|
##
|
|
@@ -318,11 +324,15 @@ module DhanHQ
|
|
|
318
324
|
# order.cancel("TARGET_LEG")
|
|
319
325
|
#
|
|
320
326
|
# @raise [RuntimeError] If order ID is missing
|
|
321
|
-
def cancel(leg_name =
|
|
327
|
+
def cancel(leg_name = DhanHQ::Constants::LegName::ENTRY_LEG)
|
|
322
328
|
raise "Order ID is required to cancel a super order" unless id
|
|
323
329
|
|
|
330
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Cancelling super order #{id} leg #{leg_name}")
|
|
324
331
|
response = self.class.resource.cancel(id, leg_name)
|
|
325
|
-
response["orderStatus"] ==
|
|
332
|
+
return false unless response.is_a?(Hash) && response["orderStatus"] == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
333
|
+
|
|
334
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Super order #{id} leg #{leg_name} cancelled successfully")
|
|
335
|
+
true
|
|
326
336
|
end
|
|
327
337
|
end
|
|
328
338
|
end
|
data/lib/DhanHQ/models/trade.rb
CHANGED
|
@@ -49,8 +49,8 @@ module DhanHQ
|
|
|
49
49
|
# Provides a shared instance of the Trades resource for current day tradebook APIs.
|
|
50
50
|
#
|
|
51
51
|
# @return [DhanHQ::Resources::Trades] The Trades resource client instance
|
|
52
|
-
def
|
|
53
|
-
@
|
|
52
|
+
def resource
|
|
53
|
+
@resource ||= DhanHQ::Resources::Trades.new
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
##
|
|
@@ -107,10 +107,8 @@ module DhanHQ
|
|
|
107
107
|
# puts "P&L: ₹#{pnl}"
|
|
108
108
|
#
|
|
109
109
|
def today
|
|
110
|
-
response =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
response.map { |trade_data| new(trade_data, skip_validation: true) }
|
|
110
|
+
response = resource.all
|
|
111
|
+
parse_collection_response(response)
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
##
|
|
@@ -145,7 +143,7 @@ module DhanHQ
|
|
|
145
143
|
raise DhanHQ::ValidationError, "Invalid order_id: #{validation_result.errors.to_h}"
|
|
146
144
|
end
|
|
147
145
|
|
|
148
|
-
response =
|
|
146
|
+
response = resource.find(order_id)
|
|
149
147
|
return nil unless response.is_a?(Hash) || (response.is_a?(Array) && response.any?)
|
|
150
148
|
|
|
151
149
|
data = response.is_a?(Array) ? response.first : response
|
|
@@ -248,16 +246,6 @@ module DhanHQ
|
|
|
248
246
|
alias all history
|
|
249
247
|
end
|
|
250
248
|
|
|
251
|
-
##
|
|
252
|
-
# Trade objects are read-only, so no validation contract needed.
|
|
253
|
-
#
|
|
254
|
-
# @return [nil] Always returns nil as trades are read-only
|
|
255
|
-
#
|
|
256
|
-
# @api private
|
|
257
|
-
def validation_contract
|
|
258
|
-
nil
|
|
259
|
-
end
|
|
260
|
-
|
|
261
249
|
##
|
|
262
250
|
# Checks if the trade is a BUY transaction.
|
|
263
251
|
#
|
|
@@ -270,7 +258,7 @@ module DhanHQ
|
|
|
270
258
|
# end
|
|
271
259
|
#
|
|
272
260
|
def buy?
|
|
273
|
-
transaction_type ==
|
|
261
|
+
transaction_type == DhanHQ::Constants::TransactionType::BUY
|
|
274
262
|
end
|
|
275
263
|
|
|
276
264
|
##
|
|
@@ -285,7 +273,7 @@ module DhanHQ
|
|
|
285
273
|
# end
|
|
286
274
|
#
|
|
287
275
|
def sell?
|
|
288
|
-
transaction_type ==
|
|
276
|
+
transaction_type == DhanHQ::Constants::TransactionType::SELL
|
|
289
277
|
end
|
|
290
278
|
|
|
291
279
|
##
|
|
@@ -299,7 +287,7 @@ module DhanHQ
|
|
|
299
287
|
# puts "Equity trades: #{equity_trades.count}"
|
|
300
288
|
#
|
|
301
289
|
def equity?
|
|
302
|
-
instrument ==
|
|
290
|
+
instrument == DhanHQ::Constants::InstrumentType::EQUITY
|
|
303
291
|
end
|
|
304
292
|
|
|
305
293
|
##
|
|
@@ -327,7 +315,7 @@ module DhanHQ
|
|
|
327
315
|
# puts "Option trades: #{option_trades.count}"
|
|
328
316
|
#
|
|
329
317
|
def option?
|
|
330
|
-
|
|
318
|
+
[DhanHQ::Constants::OptionType::CALL, DhanHQ::Constants::OptionType::PUT].include?(drv_option_type)
|
|
331
319
|
end
|
|
332
320
|
|
|
333
321
|
##
|
|
@@ -341,7 +329,7 @@ module DhanHQ
|
|
|
341
329
|
# puts "Call option trades: #{call_trades.count}"
|
|
342
330
|
#
|
|
343
331
|
def call_option?
|
|
344
|
-
drv_option_type ==
|
|
332
|
+
drv_option_type == DhanHQ::Constants::OptionType::CALL
|
|
345
333
|
end
|
|
346
334
|
|
|
347
335
|
##
|
|
@@ -355,7 +343,7 @@ module DhanHQ
|
|
|
355
343
|
# puts "Put option trades: #{put_trades.count}"
|
|
356
344
|
#
|
|
357
345
|
def put_option?
|
|
358
|
-
drv_option_type ==
|
|
346
|
+
drv_option_type == DhanHQ::Constants::OptionType::PUT
|
|
359
347
|
end
|
|
360
348
|
|
|
361
349
|
##
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Resources
|
|
5
|
-
# Resource for IP whitelist per API docs: GET /ip/getIP, POST /ip/setIP, PUT /ip/modifyIP.
|
|
5
|
+
# Resource for IP whitelist per API docs: GET /v2/ip/getIP, POST /v2/ip/setIP, PUT /v2/ip/modifyIP.
|
|
6
|
+
# Set/Modify require dhanClientId, ip, ipFlag (PRIMARY | SECONDARY). See dhanhq.co/docs/v2/authentication/#setup-static-ip
|
|
6
7
|
class IPSetup < BaseAPI
|
|
7
8
|
API_TYPE = :order_api
|
|
8
9
|
HTTP_PATH = "/ip"
|
|
@@ -11,12 +12,22 @@ module DhanHQ
|
|
|
11
12
|
get("/getIP")
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
# @param ip [String] Static IP (IPv4 or IPv6)
|
|
16
|
+
# @param ip_flag [String] "PRIMARY" or "SECONDARY"
|
|
17
|
+
# @param dhan_client_id [String, nil] Defaults to DhanHQ.configuration.client_id when nil
|
|
18
|
+
def set(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)
|
|
19
|
+
params = { ip: ip, ip_flag: ip_flag }
|
|
20
|
+
params[:dhan_client_id] = dhan_client_id || DhanHQ.configuration&.client_id
|
|
21
|
+
post("/setIP", params: params)
|
|
16
22
|
end
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
# @param ip [String] Static IP (IPv4 or IPv6)
|
|
25
|
+
# @param ip_flag [String] "PRIMARY" or "SECONDARY"
|
|
26
|
+
# @param dhan_client_id [String, nil] Defaults to DhanHQ.configuration.client_id when nil
|
|
27
|
+
def update(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)
|
|
28
|
+
params = { ip: ip, ip_flag: ip_flag }
|
|
29
|
+
params[:dhan_client_id] = dhan_client_id || DhanHQ.configuration&.client_id
|
|
30
|
+
put("/modifyIP", params: params)
|
|
20
31
|
end
|
|
21
32
|
end
|
|
22
33
|
end
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Resource client to control the trading kill switch feature.
|
|
8
|
+
# API expects killSwitchStatus as query parameter (no body). See dhanhq.co/docs/v2/traders-control/
|
|
6
9
|
class KillSwitch < BaseAPI
|
|
7
|
-
|
|
8
|
-
API_TYPE = :order_api
|
|
9
|
-
# Base path for kill switch operations.
|
|
10
|
+
API_TYPE = :order_api
|
|
10
11
|
HTTP_PATH = "/v2/killswitch"
|
|
11
12
|
|
|
12
|
-
# Enables or disables the kill switch.
|
|
13
|
+
# Enables or disables the kill switch via query parameter (doc: no body).
|
|
13
14
|
#
|
|
14
|
-
# @param
|
|
15
|
+
# @param status [String] "ACTIVATE" or "DEACTIVATE"
|
|
15
16
|
# @return [Hash]
|
|
16
|
-
def update(
|
|
17
|
-
|
|
17
|
+
def update(status)
|
|
18
|
+
query = "?killSwitchStatus=#{CGI.escape(status.to_s)}"
|
|
19
|
+
handle_response(client.post(build_path(query), {}))
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
##
|