DhanHQ 3.2.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +23 -0
  3. data/CHANGELOG.md +26 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/lib/DhanHQ/ai/prompt_helpers.rb +17 -7
  6. data/lib/DhanHQ/client.rb +53 -34
  7. data/lib/DhanHQ/models/alert_order.rb +1 -1
  8. data/lib/DhanHQ/risk/pipeline.rb +0 -2
  9. data/lib/DhanHQ/version.rb +1 -1
  10. data/lib/dhan_hq.rb +4 -0
  11. data/skills/dhanhq-ruby/SKILL.md +208 -0
  12. data/skills/dhanhq-ruby/examples/fetch_option_chain.rb +54 -0
  13. data/skills/dhanhq-ruby/examples/gtt_forever_order.rb +65 -0
  14. data/skills/dhanhq-ruby/examples/historical_data_analysis.rb +89 -0
  15. data/skills/dhanhq-ruby/examples/iron_condor.rb +137 -0
  16. data/skills/dhanhq-ruby/examples/live_feed_setup.rb +43 -0
  17. data/skills/dhanhq-ruby/examples/margin_check.rb +42 -0
  18. data/skills/dhanhq-ruby/examples/order_management.rb +112 -0
  19. data/skills/dhanhq-ruby/examples/place_equity_order.rb +36 -0
  20. data/skills/dhanhq-ruby/examples/place_fno_order.rb +76 -0
  21. data/skills/dhanhq-ruby/examples/portfolio_summary.rb +74 -0
  22. data/skills/dhanhq-ruby/examples/super_order_with_sl.rb +57 -0
  23. data/skills/dhanhq-ruby/references/backtesting-with-dhan.md +65 -0
  24. data/skills/dhanhq-ruby/references/common-workflows.md +76 -0
  25. data/skills/dhanhq-ruby/references/error-codes.md +50 -0
  26. data/skills/dhanhq-ruby/references/funds.md +67 -0
  27. data/skills/dhanhq-ruby/references/instruments.md +91 -0
  28. data/skills/dhanhq-ruby/references/live-feed.md +83 -0
  29. data/skills/dhanhq-ruby/references/market-data.md +119 -0
  30. data/skills/dhanhq-ruby/references/option-chain.md +71 -0
  31. data/skills/dhanhq-ruby/references/options-analysis-patterns.md +76 -0
  32. data/skills/dhanhq-ruby/references/orders.md +203 -0
  33. data/skills/dhanhq-ruby/references/portfolio.md +93 -0
  34. data/skills/dhanhq-ruby/references/scanx-data.md +62 -0
  35. data/skills/dhanhq-ruby/scripts/dhan_helpers.rb +323 -0
  36. data/skills/dhanhq-ruby/scripts/resolve_security.rb +168 -0
  37. data/skills/dhanhq-ruby/scripts/trade_logger.rb +131 -0
  38. data/skills/dhanhq-ruby/scripts/validate_order.rb +169 -0
  39. metadata +32 -2
@@ -0,0 +1,54 @@
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 "Using 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
+ puts "Nifty Spot: #{spot}"
29
+ puts "ATM Strike: #{atm["strike"]}"
30
+
31
+ # Filter strikes between ATM - 500 and ATM + 500
32
+ nearby = chain_df.select do |row|
33
+ row["strike"].between?(atm["strike"] - 500, atm["strike"] + 500)
34
+ end
35
+
36
+ puts "\nOption Chain (ATM ± 500 points):\n\n"
37
+ printf(
38
+ "%-10s | %-8s | %-12s | %-6s | %-8s | %-12s | %-6s\n",
39
+ "Strike", "CE LTP", "CE OI", "CE IV", "PE LTP", "PE OI", "PE IV"
40
+ )
41
+ puts "-" * 75
42
+ nearby.each do |row|
43
+ printf(
44
+ "%-10g | %-8.2f | %-12d | %-6.2f | %-8.2f | %-12d | %-6.2f\n",
45
+ row["strike"],
46
+ row["ce_ltp"].to_f,
47
+ row["ce_oi"].to_i,
48
+ row["ce_iv"].to_f,
49
+ row["pe_ltp"].to_f,
50
+ row["pe_oi"].to_i,
51
+ row["pe_iv"].to_f
52
+ )
53
+ end
54
+ puts
@@ -0,0 +1,65 @@
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
+ # Example 1: Single GTT — Buy Reliance if it dips to ₹2300
11
+ puts "--- GTT Single: Buy RELIANCE on dip ---"
12
+ # order = DhanHQ::Models::ForeverOrder.create(
13
+ # security_id: "2885",
14
+ # exchange_segment: "NSE_EQ",
15
+ # transaction_type: "BUY",
16
+ # product_type: "CNC", # Equity delivery
17
+ # order_type: "LIMIT",
18
+ # quantity: 5,
19
+ # price: 2300.00, # Limit price
20
+ # trigger_price: 2305.00, # Trigger price
21
+ # order_flag: "SINGLE",
22
+ # validity: "DAY"
23
+ # )
24
+ # puts "GTT placed: #{order.inspect}"
25
+
26
+ # Example 2: OCO — Sell Reliance at ₹2700 (target) OR ₹2200 (stop loss)
27
+ puts "\n--- GTT OCO: Target + Stop Loss for RELIANCE holding ---"
28
+ # order = DhanHQ::Models::ForeverOrder.create(
29
+ # security_id: "2885",
30
+ # exchange_segment: "NSE_EQ",
31
+ # transaction_type: "SELL",
32
+ # product_type: "CNC", # Selling from holdings
33
+ # order_type: "LIMIT",
34
+ # quantity: 5,
35
+ # price: 2700.00, # Target price (price of first leg)
36
+ # trigger_price: 2695.00, # Target trigger price (trigger of first leg)
37
+ # price1: 2200.00, # Stop loss price (price of second leg)
38
+ # trigger_price1: 2205.00, # Stop loss trigger price (trigger of second leg)
39
+ # quantity1: 5, # Stop loss quantity (quantity of second leg)
40
+ # order_flag: "OCO", # One Cancels Other
41
+ # validity: "DAY"
42
+ # )
43
+ # puts "OCO placed: #{order.inspect}"
44
+
45
+ # Example 3: List all active forever orders
46
+ puts "\n--- Active Forever Orders ---"
47
+ forever_orders = begin
48
+ DhanHQ::Models::ForeverOrder.all
49
+ rescue StandardError
50
+ []
51
+ end
52
+ if forever_orders.any?
53
+ forever_orders.each do |ord|
54
+ puts " ID: #{ord.order_id} | " \
55
+ "#{ord.trading_symbol} | " \
56
+ "Type: #{ord.order_flag} | " \
57
+ "Trigger: ₹#{ord.trigger_price}"
58
+ end
59
+ else
60
+ puts " No active forever orders"
61
+ end
62
+
63
+ # Example 4: Cancel a forever order
64
+ # order = DhanHQ::Models::ForeverOrder.find("YOUR_ORDER_ID")
65
+ # order.cancel if order
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
4
+ require "dhan_hq"
5
+ require "date"
6
+ require_relative "../scripts/dhan_helpers"
7
+
8
+ # Initialize credentials
9
+ get_client
10
+
11
+ to_date = Date.today.strftime("%Y-%m-%d")
12
+ from_date = (Date.today - 180).strftime("%Y-%m-%d")
13
+
14
+ # Fetch daily charts via HistoricalData model
15
+ candles = DhanHQ::Models::HistoricalData.daily(
16
+ security_id: "2885",
17
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
18
+ instrument: DhanHQ::Constants::InstrumentType::EQUITY,
19
+ from_date: from_date,
20
+ to_date: to_date
21
+ )
22
+
23
+ if candles.empty?
24
+ puts "No candle data returned."
25
+ exit 1
26
+ end
27
+
28
+ close_prices = candles.map { |c| c[:close].to_f }
29
+ timestamps = candles.map { |c| c[:timestamp] }
30
+
31
+ # Calculate SMAs
32
+ def calculate_sma(prices, period)
33
+ return [] if prices.size < period
34
+
35
+ # Calculate SMA for each index starting from period - 1
36
+ ((period - 1)...prices.size).map do |i|
37
+ prices[(i - period + 1)..i].sum / period.to_f
38
+ end
39
+ end
40
+
41
+ sma_20_series = calculate_sma(close_prices, 20)
42
+ sma_50_series = calculate_sma(close_prices, 50)
43
+
44
+ latest_sma_20 = sma_20_series.last
45
+ latest_sma_50 = sma_50_series.last
46
+
47
+ # Calculate returns
48
+ returns = []
49
+ close_prices.each_cons(2) do |prev_price, curr_price|
50
+ returns << ((curr_price - prev_price) / prev_price)
51
+ end
52
+
53
+ # Calculate daily volatility (standard deviation of returns) and annualize it
54
+ mean_return = returns.sum / returns.size.to_f
55
+ variance = returns.sum { |r| (r - mean_return)**2 } / (returns.size - 1).to_f
56
+ std_dev = Math.sqrt(variance)
57
+ annualized_volatility = std_dev * Math.sqrt(252)
58
+
59
+ start_date = begin
60
+ timestamps.first.is_a?(Time) ? timestamps.first.to_date : Date.parse(timestamps.first.to_s)
61
+ rescue StandardError
62
+ "N/A"
63
+ end
64
+ end_date = begin
65
+ timestamps.last.is_a?(Time) ? timestamps.last.to_date : Date.parse(timestamps.last.to_s)
66
+ rescue StandardError
67
+ "N/A"
68
+ end
69
+
70
+ puts "=== RELIANCE — Last 6 Months ===\n\n"
71
+ puts "Period: #{start_date} to #{end_date}"
72
+ puts "Trading Days: #{candles.size}"
73
+ puts "Start Price: Rs. #{"%.2f" % close_prices.first}"
74
+ puts "End Price: Rs. #{"%.2f" % close_prices.last}"
75
+ puts "High: Rs. #{"%.2f" % candles.map { |c| c[:high].to_f }.max}"
76
+ puts "Low: Rs. #{"%.2f" % candles.map { |c| c[:low].to_f }.min}"
77
+ puts "Total Return: #{format("%.2f%", ((close_prices.last / close_prices.first) - 1.0) * 100)}"
78
+ puts "Avg Daily Vol: #{format("%.0f", candles.sum { |c| c[:volume].to_i } / candles.size.to_f)}"
79
+ puts "Volatility: #{format("%.2f%", annualized_volatility * 100)} (annualized)"
80
+
81
+ if latest_sma_20 && latest_sma_50
82
+ puts "\nSMA 20: Rs. #{"%.2f" % latest_sma_20}"
83
+ puts "SMA 50: Rs. #{"%.2f" % latest_sma_50}"
84
+ if latest_sma_20 > latest_sma_50
85
+ puts "Signal: Bullish (SMA 20 > SMA 50)"
86
+ else
87
+ puts "Signal: Bearish (SMA 20 < SMA 50)"
88
+ end
89
+ end
@@ -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,112 @@
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
+ # The gem has its own independent safety gate: Order.place/modify/cancel raise
31
+ # DhanHQ::LiveTradingDisabledError unless ENV["LIVE_TRADING"]="true" is also set.
32
+ if ENV["LIVE_TRADING"] != "true"
33
+ puts "Also set ENV['LIVE_TRADING']='true' — the SDK blocks order placement without it."
34
+ exit 0
35
+ end
36
+
37
+ # Step 1: Place a limit order (well below market for demo — won't fill)
38
+ puts "Step 1: Placing limit buy order for RELIANCE..."
39
+ order = DhanHQ::Models::Order.place(
40
+ security_id: security_id,
41
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
42
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
43
+ quantity: quantity,
44
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
45
+ product_type: DhanHQ::Constants::ProductType::CNC,
46
+ price: price, # Below market — will stay pending
47
+ validity: DhanHQ::Constants::Validity::DAY
48
+ )
49
+
50
+ if order.nil? || order.order_id.to_s.empty?
51
+ puts "Order failed to place."
52
+ exit 1
53
+ end
54
+
55
+ order_id = order.order_id
56
+ puts "Order placed: #{order_id}"
57
+
58
+ # Step 2: Check order status
59
+ puts "\nStep 2: Checking order status..."
60
+ sleep(1)
61
+ order = DhanHQ::Models::Order.find(order_id)
62
+ status = order.order_status
63
+ puts "Status: #{status}"
64
+ puts " Security: #{order.trading_symbol}"
65
+ puts " Qty: #{order.quantity}"
66
+ puts " Price: ₹#{order.price}"
67
+ puts " Filled: #{order.filled_qty || 0}"
68
+
69
+ # Step 3: Modify the order (change price)
70
+ if status == DhanHQ::Constants::OrderStatus::PENDING
71
+ puts "\nStep 3: Modifying order price to ₹2050..."
72
+ modified_order = order.modify(
73
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
74
+ quantity: quantity,
75
+ price: 2050.00,
76
+ validity: DhanHQ::Constants::Validity::DAY
77
+ )
78
+ puts "Modify result: #{modified_order ? "Success" : "Failure"}"
79
+ end
80
+
81
+ # Step 4: Cancel the order
82
+ puts "\nStep 4: Cancelling order..."
83
+ cancel_success = order.cancel
84
+ puts "Cancel result: #{cancel_success ? "Success" : "Failure"}"
85
+
86
+ # Step 5: View order book
87
+ puts "\nStep 5: Today's order book:"
88
+ orders = begin
89
+ DhanHQ::Models::Order.all
90
+ rescue StandardError
91
+ []
92
+ end
93
+ if orders.any?
94
+ orders.last(5).each do |o| # Last 5 orders
95
+ 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)
96
+ end
97
+ end
98
+
99
+ # Step 6: View trade book
100
+ puts "\nStep 6: Today's trade book:"
101
+ trades = begin
102
+ DhanHQ::Models::Trade.today
103
+ rescue StandardError
104
+ []
105
+ end
106
+ if trades.any?
107
+ trades.last(5).each do |t|
108
+ printf(" %-12s | %-4s | Qty: %-5d | ₹%-8.2f\n", t.trading_symbol, t.transaction_type, t.traded_quantity, t.traded_price.to_f)
109
+ end
110
+ else
111
+ puts " No trades today"
112
+ 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}"