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.
- checksums.yaml +4 -4
- data/AGENTS.md +23 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/lib/DhanHQ/ai/prompt_helpers.rb +17 -7
- data/lib/DhanHQ/client.rb +53 -34
- data/lib/DhanHQ/models/alert_order.rb +1 -1
- data/lib/DhanHQ/risk/pipeline.rb +0 -2
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/dhan_hq.rb +4 -0
- data/skills/dhanhq-ruby/SKILL.md +208 -0
- data/skills/dhanhq-ruby/examples/fetch_option_chain.rb +54 -0
- data/skills/dhanhq-ruby/examples/gtt_forever_order.rb +65 -0
- data/skills/dhanhq-ruby/examples/historical_data_analysis.rb +89 -0
- data/skills/dhanhq-ruby/examples/iron_condor.rb +137 -0
- data/skills/dhanhq-ruby/examples/live_feed_setup.rb +43 -0
- data/skills/dhanhq-ruby/examples/margin_check.rb +42 -0
- data/skills/dhanhq-ruby/examples/order_management.rb +112 -0
- data/skills/dhanhq-ruby/examples/place_equity_order.rb +36 -0
- data/skills/dhanhq-ruby/examples/place_fno_order.rb +76 -0
- data/skills/dhanhq-ruby/examples/portfolio_summary.rb +74 -0
- data/skills/dhanhq-ruby/examples/super_order_with_sl.rb +57 -0
- data/skills/dhanhq-ruby/references/backtesting-with-dhan.md +65 -0
- data/skills/dhanhq-ruby/references/common-workflows.md +76 -0
- data/skills/dhanhq-ruby/references/error-codes.md +50 -0
- data/skills/dhanhq-ruby/references/funds.md +67 -0
- data/skills/dhanhq-ruby/references/instruments.md +91 -0
- data/skills/dhanhq-ruby/references/live-feed.md +83 -0
- data/skills/dhanhq-ruby/references/market-data.md +119 -0
- data/skills/dhanhq-ruby/references/option-chain.md +71 -0
- data/skills/dhanhq-ruby/references/options-analysis-patterns.md +76 -0
- data/skills/dhanhq-ruby/references/orders.md +203 -0
- data/skills/dhanhq-ruby/references/portfolio.md +93 -0
- data/skills/dhanhq-ruby/references/scanx-data.md +62 -0
- data/skills/dhanhq-ruby/scripts/dhan_helpers.rb +323 -0
- data/skills/dhanhq-ruby/scripts/resolve_security.rb +168 -0
- data/skills/dhanhq-ruby/scripts/trade_logger.rb +131 -0
- data/skills/dhanhq-ruby/scripts/validate_order.rb +169 -0
- 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}"
|