DhanHQ 3.0.1 → 3.1.0

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.yml +2 -0
  3. data/CHANGELOG.md +51 -0
  4. data/README.md +152 -4
  5. data/docs/CONSTANTS_REFERENCE.md +3 -2
  6. data/exe/dhanhq-mcp +7 -0
  7. data/lib/DhanHQ/agent/tool_registry.rb +51 -2
  8. data/lib/DhanHQ/concerns/order_audit.rb +43 -1
  9. data/lib/DhanHQ/constants.rb +3 -2
  10. data/lib/DhanHQ/contracts/forever_order_contract.rb +1 -1
  11. data/lib/DhanHQ/contracts/iceberg_order_contract.rb +1 -1
  12. data/lib/DhanHQ/contracts/place_order_contract.rb +1 -1
  13. data/lib/DhanHQ/contracts/twap_order_contract.rb +1 -1
  14. data/lib/DhanHQ/mcp/server.rb +172 -9
  15. data/lib/DhanHQ/models/instrument.rb +44 -14
  16. data/lib/DhanHQ/rate_limiter.rb +5 -3
  17. data/lib/DhanHQ/resources/alert_orders.rb +1 -0
  18. data/lib/DhanHQ/resources/forever_orders.rb +1 -0
  19. data/lib/DhanHQ/resources/iceberg_orders.rb +1 -0
  20. data/lib/DhanHQ/resources/orders.rb +2 -0
  21. data/lib/DhanHQ/resources/pnl_exit.rb +1 -0
  22. data/lib/DhanHQ/resources/super_orders.rb +1 -0
  23. data/lib/DhanHQ/resources/twap_orders.rb +1 -0
  24. data/lib/DhanHQ/risk/checks/concentration.rb +37 -0
  25. data/lib/DhanHQ/risk/checks/max_loss.rb +24 -0
  26. data/lib/DhanHQ/risk/checks/position_limits.rb +24 -0
  27. data/lib/DhanHQ/risk/pipeline.rb +8 -1
  28. data/lib/DhanHQ/skills/base.rb +54 -3
  29. data/lib/DhanHQ/skills/builtin/bear_call_spread.rb +87 -0
  30. data/lib/DhanHQ/skills/builtin/bull_put_spread.rb +87 -0
  31. data/lib/DhanHQ/skills/builtin/buy_atm_call.rb +10 -13
  32. data/lib/DhanHQ/skills/builtin/covered_call.rb +85 -0
  33. data/lib/DhanHQ/skills/builtin/iron_condor.rb +15 -19
  34. data/lib/DhanHQ/skills/builtin/market_data_summarizer.rb +195 -0
  35. data/lib/DhanHQ/skills/builtin/protective_put.rb +90 -0
  36. data/lib/DhanHQ/skills/builtin/square_off_all.rb +5 -8
  37. data/lib/DhanHQ/skills/builtin/square_off_position.rb +8 -6
  38. data/lib/DhanHQ/skills/builtin/straddle.rb +88 -0
  39. data/lib/DhanHQ/skills/builtin/strangle.rb +13 -13
  40. data/lib/DhanHQ/version.rb +1 -1
  41. data/lib/dhan_hq.rb +47 -0
  42. data/skills/dhanhq-ruby/SKILL.md +174 -41
  43. data/skills/dhanhq-ruby/examples/fetch_option_chain.rb +54 -0
  44. data/skills/dhanhq-ruby/examples/gtt_forever_order.rb +65 -0
  45. data/skills/dhanhq-ruby/examples/historical_data_analysis.rb +89 -0
  46. data/skills/dhanhq-ruby/examples/iron_condor.rb +137 -0
  47. data/skills/dhanhq-ruby/examples/live_feed_setup.rb +43 -0
  48. data/skills/dhanhq-ruby/examples/margin_check.rb +42 -0
  49. data/skills/dhanhq-ruby/examples/order_management.rb +105 -0
  50. data/skills/dhanhq-ruby/examples/place_equity_order.rb +36 -0
  51. data/skills/dhanhq-ruby/examples/place_fno_order.rb +76 -0
  52. data/skills/dhanhq-ruby/examples/portfolio_summary.rb +74 -0
  53. data/skills/dhanhq-ruby/examples/super_order_with_sl.rb +57 -0
  54. data/skills/dhanhq-ruby/references/backtesting-with-dhan.md +65 -0
  55. data/skills/dhanhq-ruby/references/common-workflows.md +76 -0
  56. data/skills/dhanhq-ruby/references/error-codes.md +50 -0
  57. data/skills/dhanhq-ruby/references/funds.md +67 -0
  58. data/skills/dhanhq-ruby/references/instruments.md +85 -0
  59. data/skills/dhanhq-ruby/references/live-feed.md +83 -0
  60. data/skills/dhanhq-ruby/references/market-data.md +119 -0
  61. data/skills/dhanhq-ruby/references/option-chain.md +71 -0
  62. data/skills/dhanhq-ruby/references/options-analysis-patterns.md +76 -0
  63. data/skills/dhanhq-ruby/references/orders.md +200 -6
  64. data/skills/dhanhq-ruby/references/portfolio.md +93 -0
  65. data/skills/dhanhq-ruby/references/scanx-data.md +62 -0
  66. data/skills/dhanhq-ruby/scripts/dhan_helpers.rb +323 -0
  67. data/skills/dhanhq-ruby/scripts/resolve_security.rb +168 -0
  68. data/skills/dhanhq-ruby/scripts/trade_logger.rb +131 -0
  69. data/skills/dhanhq-ruby/scripts/validate_order.rb +169 -0
  70. metadata +39 -3
  71. data/skills/dhanhq-ruby/references/market_data.md +0 -3
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(underlying_scrip: 13, underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I)
11
+ nearest_expiry = expiries.first
12
+
13
+ if nearest_expiry.nil?
14
+ puts "Failed to fetch expiries."
15
+ exit 1
16
+ end
17
+
18
+ chain_df, spot = fetch_chain_df(under_security_id: 13, expiry: nearest_expiry)
19
+ puts "Nifty Spot: #{spot}, Expiry: #{nearest_expiry}"
20
+
21
+ strike_prices = chain_df.map { |r| r["strike"] }.sort
22
+ sell_ce_strike = strike_prices.min_by { |x| (x - (spot + 200)).abs }
23
+ buy_ce_strike = sell_ce_strike + 200
24
+ sell_pe_strike = strike_prices.min_by { |x| (x - (spot - 200)).abs }
25
+ buy_pe_strike = sell_pe_strike - 200
26
+
27
+ def get_row(chain_df, target_strike)
28
+ chain_df.find { |r| r["strike"] == target_strike }
29
+ end
30
+
31
+ sell_ce = get_row(chain_df, sell_ce_strike)
32
+ buy_ce = get_row(chain_df, buy_ce_strike)
33
+ sell_pe = get_row(chain_df, sell_pe_strike)
34
+ buy_pe = get_row(chain_df, buy_pe_strike)
35
+
36
+ if [sell_ce, buy_ce, sell_pe, buy_pe].any?(&:nil?)
37
+ puts "Could not find all required strikes. Try different offsets."
38
+ exit 1
39
+ end
40
+
41
+ lot_size = get_lot_size(underlying: "NIFTY") || 75
42
+ legs = [
43
+ {
44
+ "label" => "Sell #{sell_pe_strike.to_i} PE",
45
+ "type" => "PE",
46
+ "strike" => sell_pe_strike,
47
+ "premium" => sell_pe["pe_ltp"].to_f,
48
+ "qty" => -1,
49
+ "sid" => sell_pe["pe_security_id"]
50
+ },
51
+ {
52
+ "label" => "Buy #{buy_pe_strike.to_i} PE",
53
+ "type" => "PE",
54
+ "strike" => buy_pe_strike,
55
+ "premium" => buy_pe["pe_ltp"].to_f,
56
+ "qty" => 1,
57
+ "sid" => buy_pe["pe_security_id"]
58
+ },
59
+ {
60
+ "label" => "Sell #{sell_ce_strike.to_i} CE",
61
+ "type" => "CE",
62
+ "strike" => sell_ce_strike,
63
+ "premium" => sell_ce["ce_ltp"].to_f,
64
+ "qty" => -1,
65
+ "sid" => sell_ce["ce_security_id"]
66
+ },
67
+ {
68
+ "label" => "Buy #{buy_ce_strike.to_i} CE",
69
+ "type" => "CE",
70
+ "strike" => buy_ce_strike,
71
+ "premium" => buy_ce["ce_ltp"].to_f,
72
+ "qty" => 1,
73
+ "sid" => buy_ce["ce_security_id"]
74
+ }
75
+ ]
76
+
77
+ net_premium = legs.sum { |leg| -leg["qty"] * leg["premium"] }
78
+
79
+ # Evaluate payoffs
80
+ spot_range = []
81
+ current_spot = spot - 1000
82
+ while current_spot <= spot + 1000
83
+ spot_range << current_spot
84
+ current_spot += 10
85
+ end
86
+
87
+ payoffs = spot_range.map do |s|
88
+ payoff_sum = 0.0
89
+ legs.each do |leg|
90
+ intrinsic = if leg["type"] == "CE"
91
+ [s - leg["strike"], 0.0].max
92
+ else
93
+ [leg["strike"] - s, 0.0].max
94
+ end
95
+ payoff_sum += (intrinsic - leg["premium"]) * leg["qty"] * lot_size
96
+ end
97
+ payoff_sum
98
+ end
99
+
100
+ max_profit = payoffs.max
101
+ max_loss = payoffs.min
102
+
103
+ # Find breakevens where sign changes
104
+ breakevens = []
105
+ (0...(payoffs.size - 1)).each do |i|
106
+ next unless (payoffs[i] >= 0 && payoffs[i + 1].negative?) || (payoffs[i].negative? && payoffs[i + 1] >= 0)
107
+
108
+ # Linear interpolation for zero crossing
109
+ x1 = spot_range[i]
110
+ y1 = payoffs[i]
111
+ x2 = spot_range[i + 1]
112
+ y2 = payoffs[i + 1]
113
+ zero_spot = x1 - (y1 * (x2 - x1) / (y2 - y1))
114
+ breakevens << zero_spot
115
+ end
116
+
117
+ puts "\n======================================================="
118
+ puts " NIFTY IRON CONDOR — Expiry: #{nearest_expiry}"
119
+ puts "======================================================="
120
+ puts "\n Legs:"
121
+ legs.each do |leg|
122
+ action = leg["qty"].negative? ? DhanHQ::Constants::TransactionType::SELL : "BUY "
123
+ puts " #{action} 1 lot #{leg["label"]} @ Rs. #{"%.1f" % leg["premium"]}"
124
+ end
125
+
126
+ puts "\n Analysis (1 lot = #{lot_size} qty):"
127
+ printf(" Net Premium: Rs. %8.0f (%s)\n", net_premium * lot_size, net_premium.positive? ? "credit" : "debit")
128
+ printf(" Max Profit: Rs. %8.0f\n", max_profit)
129
+ printf(" Max Loss: Rs. %8.0f\n", max_loss)
130
+ puts " Breakevens: #{breakevens.map { |b| "%.0f" % b }.join(", ")}"
131
+ puts " Risk/Reward: 1:#{format("%.1f", max_profit / max_loss.abs)}" if max_loss != 0
132
+
133
+ puts "\n Orders to place after confirmation:"
134
+ legs.each do |leg|
135
+ action = leg["qty"].negative? ? DhanHQ::Constants::TransactionType::SELL : DhanHQ::Constants::TransactionType::BUY
136
+ puts " #{action} #{lot_size} qty | SID: #{leg["sid"]} | Rs. #{"%.1f" % leg["premium"]}"
137
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ puts "Starting live market feed... (Ctrl+C to stop)"
11
+
12
+ # Connect in Ticker mode (LTP updates)
13
+ # Other available modes: :quote (OHLC + Volume), :full (full depth)
14
+ market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
15
+ timestamp = tick[:ts] ? Time.at(tick[:ts]) : Time.now
16
+ puts "Tick Received -> Segment: #{tick[:segment]}, SecID: #{tick[:security_id]}, LTP: #{tick[:ltp]} at #{timestamp}"
17
+ end
18
+
19
+ # Subscribe to target instruments
20
+ # segment must match exchange segment constants from Constants, e.g. "NSE_EQ", "IDX_I", etc.
21
+ market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "2885") # RELIANCE
22
+ market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "1333") # HDFCBANK
23
+ market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "11536") # TCS
24
+
25
+ begin
26
+ # Wait for feed to stream ticks
27
+ sleep(15)
28
+ rescue Interrupt
29
+ puts "\nStopping due to interrupt..."
30
+ ensure
31
+ puts "Shutting down WebSocket..."
32
+ begin
33
+ market_client.stop
34
+ rescue StandardError
35
+ nil
36
+ end
37
+ begin
38
+ DhanHQ::WS.disconnect_all_local!
39
+ rescue StandardError
40
+ nil
41
+ end
42
+ puts "Feed stopped."
43
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ funds = DhanHQ::Models::Funds.fetch
11
+ available = funds.availabel_balance || funds.available_balance || 0.0
12
+ puts "Available Balance: Rs. #{"%.2f" % available}"
13
+
14
+ expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(underlying_scrip: 13, underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I)
15
+ nearest_expiry = expiries.first
16
+
17
+ chain_df, spot = fetch_chain_df(under_security_id: 13, expiry: nearest_expiry)
18
+ atm = find_atm_row(chain_df, spot)
19
+
20
+ if atm
21
+ puts "\n--- Margin Check: Buy 1 Lot Nifty CE (INTRADAY) ---"
22
+ option_margin = check_margin(
23
+ security_id: atm["ce_security_id"],
24
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
25
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
26
+ quantity: 75,
27
+ product_type: DhanHQ::Constants::ProductType::INTRADAY,
28
+ price: atm["ce_ltp"].to_f
29
+ )
30
+ puts option_margin.inspect
31
+ end
32
+
33
+ puts "\n--- Margin Check: Buy 10 RELIANCE (CNC Delivery) ---"
34
+ equity_margin = check_margin(
35
+ security_id: "2885",
36
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
37
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
38
+ quantity: 10,
39
+ product_type: DhanHQ::Constants::ProductType::CNC,
40
+ price: 2450.0
41
+ )
42
+ puts equity_margin.inspect
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ security_id = "2885"
11
+ price = 2000.0
12
+ quantity = 1
13
+
14
+ puts preview_order(
15
+ security_id: security_id,
16
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
17
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
18
+ quantity: quantity,
19
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
20
+ product_type: DhanHQ::Constants::ProductType::CNC,
21
+ price: price,
22
+ trading_symbol: "RELIANCE"
23
+ )
24
+
25
+ if ENV["RUN_LIVE_EXAMPLE"] != "1"
26
+ puts "Set RUN_LIVE_EXAMPLE=1 to place, modify, and cancel a live demo order."
27
+ exit 0
28
+ end
29
+
30
+ # Step 1: Place a limit order (well below market for demo — won't fill)
31
+ puts "Step 1: Placing limit buy order for RELIANCE..."
32
+ order = DhanHQ::Models::Order.place(
33
+ security_id: security_id,
34
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
35
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
36
+ quantity: quantity,
37
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
38
+ product_type: DhanHQ::Constants::ProductType::CNC,
39
+ price: price, # Below market — will stay pending
40
+ validity: DhanHQ::Constants::Validity::DAY
41
+ )
42
+
43
+ if order.nil? || order.order_id.to_s.empty?
44
+ puts "Order failed to place."
45
+ exit 1
46
+ end
47
+
48
+ order_id = order.order_id
49
+ puts "Order placed: #{order_id}"
50
+
51
+ # Step 2: Check order status
52
+ puts "\nStep 2: Checking order status..."
53
+ sleep(1)
54
+ order = DhanHQ::Models::Order.find(order_id)
55
+ status = order.order_status
56
+ puts "Status: #{status}"
57
+ puts " Security: #{order.trading_symbol}"
58
+ puts " Qty: #{order.quantity}"
59
+ puts " Price: ₹#{order.price}"
60
+ puts " Filled: #{order.filled_qty || 0}"
61
+
62
+ # Step 3: Modify the order (change price)
63
+ if status == DhanHQ::Constants::OrderStatus::PENDING
64
+ puts "\nStep 3: Modifying order price to ₹2050..."
65
+ modified_order = order.modify(
66
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
67
+ quantity: quantity,
68
+ price: 2050.00,
69
+ validity: DhanHQ::Constants::Validity::DAY
70
+ )
71
+ puts "Modify result: #{modified_order ? "Success" : "Failure"}"
72
+ end
73
+
74
+ # Step 4: Cancel the order
75
+ puts "\nStep 4: Cancelling order..."
76
+ cancel_success = order.cancel
77
+ puts "Cancel result: #{cancel_success ? "Success" : "Failure"}"
78
+
79
+ # Step 5: View order book
80
+ puts "\nStep 5: Today's order book:"
81
+ orders = begin
82
+ DhanHQ::Models::Order.all
83
+ rescue StandardError
84
+ []
85
+ end
86
+ if orders.any?
87
+ orders.last(5).each do |o| # Last 5 orders
88
+ printf(" %-12s | %-12s | %-4s | %-12s | ₹%-8.2f\n", o.order_id.to_s[0...12], o.trading_symbol, o.transaction_type, o.order_status, o.price.to_f)
89
+ end
90
+ end
91
+
92
+ # Step 6: View trade book
93
+ puts "\nStep 6: Today's trade book:"
94
+ trades = begin
95
+ DhanHQ::Models::Trade.today
96
+ rescue StandardError
97
+ []
98
+ end
99
+ if trades.any?
100
+ trades.last(5).each do |t|
101
+ printf(" %-12s | %-4s | Qty: %-5d | ₹%-8.2f\n", t.trading_symbol, t.transaction_type, t.traded_quantity, t.traded_price.to_f)
102
+ end
103
+ else
104
+ puts " No trades today"
105
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ security_id = "2885" # RELIANCE
11
+ price = 2450.0
12
+ quantity = 1
13
+
14
+ puts preview_order(
15
+ security_id: security_id,
16
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
17
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
18
+ quantity: quantity,
19
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
20
+ product_type: DhanHQ::Constants::ProductType::CNC,
21
+ price: price,
22
+ trading_symbol: "RELIANCE"
23
+ )
24
+
25
+ # Uncomment after confirmation:
26
+ # order = DhanHQ::Models::Order.place(
27
+ # security_id: security_id,
28
+ # exchange_segment: "NSE_EQ",
29
+ # transaction_type: "BUY",
30
+ # quantity: quantity,
31
+ # order_type: "LIMIT",
32
+ # product_type: "CNC",
33
+ # price: price,
34
+ # validity: "DAY"
35
+ # )
36
+ # puts "Placed order ID: #{order.order_id}"
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(underlying_scrip: 13, underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I)
11
+ nearest_expiry = expiries.first
12
+
13
+ if nearest_expiry.nil?
14
+ puts "Failed to fetch expiries."
15
+ exit 1
16
+ end
17
+
18
+ puts "Nearest expiry: #{nearest_expiry}"
19
+
20
+ chain_df, spot = fetch_chain_df(under_security_id: 13, expiry: nearest_expiry)
21
+ atm = find_atm_row(chain_df, spot)
22
+
23
+ if atm.nil?
24
+ puts "Failed to find ATM row."
25
+ exit 1
26
+ end
27
+
28
+ ce_security_id = atm["ce_security_id"]
29
+ ce_ltp = atm["ce_ltp"].to_f
30
+ lot_size = get_lot_size(underlying: "NIFTY") || 75
31
+ quantity = lot_size
32
+
33
+ puts "Nifty spot: #{spot}"
34
+ puts "ATM strike: #{atm["strike"]}"
35
+ puts "CE security ID: #{ce_security_id}, LTP: Rs. #{"%.2f" % ce_ltp}"
36
+ puts
37
+
38
+ puts preview_order(
39
+ security_id: ce_security_id,
40
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
41
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
42
+ quantity: quantity,
43
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
44
+ product_type: DhanHQ::Constants::ProductType::INTRADAY,
45
+ price: ce_ltp,
46
+ trading_symbol: "NIFTY #{atm["strike"].to_i} CE"
47
+ )
48
+
49
+ margin = check_margin(
50
+ security_id: ce_security_id,
51
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
52
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
53
+ quantity: quantity,
54
+ product_type: DhanHQ::Constants::ProductType::INTRADAY,
55
+ price: ce_ltp
56
+ )
57
+
58
+ printf(
59
+ "Margin check: sufficient=%s required=Rs. %s available=Rs. %s\n",
60
+ margin["sufficient"].to_s,
61
+ "%.2f" % margin["total_margin"],
62
+ "%.2f" % margin["available_balance"]
63
+ )
64
+
65
+ # Uncomment only after confirmation:
66
+ # order = DhanHQ::Models::Order.place(
67
+ # security_id: ce_security_id,
68
+ # exchange_segment: "NSE_FNO",
69
+ # transaction_type: "BUY",
70
+ # quantity: quantity,
71
+ # order_type: "LIMIT",
72
+ # product_type: "INTRADAY",
73
+ # price: ce_ltp,
74
+ # validity: "DAY"
75
+ # )
76
+ # puts "Placed order ID: #{order.order_id}"
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ holdings = begin
11
+ DhanHQ::Models::Holding.all
12
+ rescue StandardError
13
+ []
14
+ end
15
+ positions = begin
16
+ DhanHQ::Models::Position.all
17
+ rescue StandardError
18
+ []
19
+ end
20
+ funds = begin
21
+ DhanHQ::Models::Funds.fetch
22
+ rescue StandardError
23
+ nil
24
+ end
25
+ trades = begin
26
+ DhanHQ::Models::Trade.today
27
+ rescue StandardError
28
+ []
29
+ end
30
+
31
+ summary = format_pnl_report(holdings, positions)
32
+
33
+ puts "=" * 50
34
+ puts " PORTFOLIO SUMMARY"
35
+ puts "=" * 50
36
+ puts "\nHoldings count: #{summary["holdings_count"]}"
37
+ puts "Positions count: #{summary["positions_count"]}"
38
+ printf("Current value: Rs. %12.2f\n", summary["current_value"])
39
+ printf("Total P&L: Rs. %12.2f\n", summary["total_pnl"])
40
+ printf("Day P&L: Rs. %12.2f\n", summary["day_pnl"])
41
+
42
+ if funds
43
+ available = funds.availabel_balance || funds.available_balance || 0.0
44
+ utilized = funds.utilized_amount || 0.0
45
+ collateral = funds.collateral_amount || 0.0
46
+ withdrawable = funds.withdrawable_balance || 0.0
47
+
48
+ puts "\nFUNDS"
49
+ printf(" Available: Rs. %12.2f\n", available)
50
+ printf(" Utilized: Rs. %12.2f\n", utilized)
51
+ printf(" Collateral: Rs. %12.2f\n", collateral)
52
+ printf(" Withdrawable: Rs. %12.2f\n", withdrawable)
53
+ end
54
+
55
+ if holdings.any?
56
+ puts "\nTOP HOLDINGS"
57
+ # Sort holdings by quantity
58
+ sorted_holdings = holdings.sort_by { |h| -(h.total_qty || 0) }
59
+ sorted_holdings.first(5).each do |holding|
60
+ printf(" %-15s qty=%5d available=%5d\n", holding.trading_symbol, holding.total_qty.to_i, holding.available_qty.to_i)
61
+ end
62
+ end
63
+
64
+ open_positions = positions.reject { |p| p.net_qty.to_i.zero? }
65
+ if open_positions.any?
66
+ puts "\nOPEN POSITIONS"
67
+ open_positions.first(5).each do |position|
68
+ pnl = position.realized_profit.to_f + position.unrealized_profit.to_f
69
+ printf(" %-20s netQty=%5d pnl=Rs. %8.0f\n", position.trading_symbol, position.net_qty.to_i, pnl)
70
+ end
71
+ end
72
+
73
+ puts "\nTrades today: #{trades.size}"
74
+ puts "=" * 50
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require_relative "../scripts/dhan_helpers"
6
+
7
+ # Initialize credentials
8
+ get_client
9
+
10
+ # Fetch LTP for Reliance
11
+ ltp_response = DhanHQ::Models::MarketFeed.ltp("NSE_EQ" => [2885])
12
+ if ltp_response[:status] != "success"
13
+ puts "Failed to fetch LTP: #{ltp_response[:remarks]}"
14
+ exit 1
15
+ end
16
+
17
+ reliance_ltp = ltp_response[:data][DhanHQ::Constants::ExchangeSegment::NSE_EQ]["2885"]["last_price"].to_f
18
+ puts "Reliance LTP: Rs. #{"%.2f" % reliance_ltp}"
19
+
20
+ entry_price = reliance_ltp
21
+ target_price = (entry_price * 1.02).round(2)
22
+ sl_price = (entry_price * 0.99).round(2)
23
+ trailing_jump = 5.0
24
+
25
+ puts "\n--- Super Order Preview ---"
26
+ puts "Action: BUY 1 share of RELIANCE"
27
+ puts "Entry Price: Rs. #{"%.2f" % entry_price}"
28
+ puts "Target: Rs. #{"%.2f" % target_price}"
29
+ puts "Stop Loss: Rs. #{"%.2f" % sl_price}"
30
+ puts "Trailing Jump: Rs. #{"%.2f" % trailing_jump}"
31
+ puts "Product: INTRADAY"
32
+
33
+ # Uncomment after confirmation:
34
+ # order = DhanHQ::Models::SuperOrder.create(
35
+ # security_id: "2885",
36
+ # exchange_segment: "NSE_EQ",
37
+ # transaction_type: "BUY",
38
+ # quantity: 1,
39
+ # order_type: "LIMIT",
40
+ # product_type: "INTRADAY",
41
+ # price: entry_price,
42
+ # target_price: target_price,
43
+ # stop_loss_price: sl_price,
44
+ # trailing_jump: trailing_jump
45
+ # )
46
+ #
47
+ # if order
48
+ # puts "Super order placed: #{order.order_id} - #{order.order_status}"
49
+ #
50
+ # # Connect to Order Update websocket to listen for updates
51
+ # orders_client = DhanHQ::WS::Orders.connect do |update|
52
+ # puts "Order Update -> OrderNo: #{update.order_no}, Status: #{update.status}"
53
+ # end
54
+ #
55
+ # sleep(10)
56
+ # orders_client.stop rescue nil
57
+ # end
@@ -0,0 +1,65 @@
1
+ # Backtesting With Dhan Data (Ruby SDK)
2
+
3
+ ## Daily Equity Backtest Skeleton
4
+
5
+ ```ruby
6
+ # Fetch daily charts via HistoricalData model
7
+ candles = DhanHQ::Models::HistoricalData.daily(
8
+ security_id: "2885",
9
+ exchange_segment: "NSE_EQ",
10
+ instrument: "EQUITY",
11
+ from_date: "2023-01-01",
12
+ to_date: "2024-12-31"
13
+ )
14
+
15
+ # candles is a normalized array of hashes:
16
+ # [{ timestamp: Time, open: Float, high: Float, low: Float, close: Float, volume: Integer }]
17
+ ```
18
+
19
+ Typical next steps:
20
+ - Create signals based on technical calculations.
21
+ - Shift positions to avoid look-ahead bias.
22
+ - Apply transaction costs.
23
+ - Compute CAGR, maximum drawdown, Sharpe ratio, and win rate.
24
+
25
+ ## Minute-Level Backtest Skeleton
26
+
27
+ ```ruby
28
+ candles = DhanHQ::Models::HistoricalData.intraday(
29
+ security_id: "2885",
30
+ exchange_segment: "NSE_EQ",
31
+ instrument: "EQUITY",
32
+ from_date: "2024-09-11 09:30:00",
33
+ to_date: "2024-09-15 13:00:00",
34
+ interval: "5", # 5-minute interval
35
+ oi: false
36
+ )
37
+ ```
38
+
39
+ ## Expired Options Backtest Skeleton
40
+
41
+ ```ruby
42
+ response = DhanHQ::Models::ExpiredOptionsData.fetch(
43
+ underlying_scrip: 13,
44
+ exchange_segment: "NSE_FNO",
45
+ expiry_flag: "MONTH",
46
+ expiry_code: 1,
47
+ strike: "ATM",
48
+ option_type: "CALL",
49
+ required_data: ["open", "high", "low", "close", "volume", "oi", "spot"],
50
+ from_date: "2021-08-01",
51
+ to_date: "2021-08-31",
52
+ interval: "1"
53
+ )
54
+ ```
55
+
56
+ ## Cost Model Reminders
57
+
58
+ At minimum consider:
59
+ - Brokerage charges
60
+ - Securities Transaction Tax (STT)
61
+ - Exchange transaction charges
62
+ - GST (Service Tax)
63
+ - Stamp duty
64
+ - SEBI turnover charges
65
+ - Slippage