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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +51 -0
- data/README.md +152 -4
- data/docs/CONSTANTS_REFERENCE.md +3 -2
- data/exe/dhanhq-mcp +7 -0
- data/lib/DhanHQ/agent/tool_registry.rb +51 -2
- data/lib/DhanHQ/concerns/order_audit.rb +43 -1
- data/lib/DhanHQ/constants.rb +3 -2
- data/lib/DhanHQ/contracts/forever_order_contract.rb +1 -1
- data/lib/DhanHQ/contracts/iceberg_order_contract.rb +1 -1
- data/lib/DhanHQ/contracts/place_order_contract.rb +1 -1
- data/lib/DhanHQ/contracts/twap_order_contract.rb +1 -1
- data/lib/DhanHQ/mcp/server.rb +172 -9
- data/lib/DhanHQ/models/instrument.rb +44 -14
- data/lib/DhanHQ/rate_limiter.rb +5 -3
- data/lib/DhanHQ/resources/alert_orders.rb +1 -0
- data/lib/DhanHQ/resources/forever_orders.rb +1 -0
- data/lib/DhanHQ/resources/iceberg_orders.rb +1 -0
- data/lib/DhanHQ/resources/orders.rb +2 -0
- data/lib/DhanHQ/resources/pnl_exit.rb +1 -0
- data/lib/DhanHQ/resources/super_orders.rb +1 -0
- data/lib/DhanHQ/resources/twap_orders.rb +1 -0
- data/lib/DhanHQ/risk/checks/concentration.rb +37 -0
- data/lib/DhanHQ/risk/checks/max_loss.rb +24 -0
- data/lib/DhanHQ/risk/checks/position_limits.rb +24 -0
- data/lib/DhanHQ/risk/pipeline.rb +8 -1
- data/lib/DhanHQ/skills/base.rb +54 -3
- data/lib/DhanHQ/skills/builtin/bear_call_spread.rb +87 -0
- data/lib/DhanHQ/skills/builtin/bull_put_spread.rb +87 -0
- data/lib/DhanHQ/skills/builtin/buy_atm_call.rb +10 -13
- data/lib/DhanHQ/skills/builtin/covered_call.rb +85 -0
- data/lib/DhanHQ/skills/builtin/iron_condor.rb +15 -19
- data/lib/DhanHQ/skills/builtin/market_data_summarizer.rb +195 -0
- data/lib/DhanHQ/skills/builtin/protective_put.rb +90 -0
- data/lib/DhanHQ/skills/builtin/square_off_all.rb +5 -8
- data/lib/DhanHQ/skills/builtin/square_off_position.rb +8 -6
- data/lib/DhanHQ/skills/builtin/straddle.rb +88 -0
- data/lib/DhanHQ/skills/builtin/strangle.rb +13 -13
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/dhan_hq.rb +47 -0
- data/skills/dhanhq-ruby/SKILL.md +174 -41
- 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 +105 -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 +85 -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 +200 -6
- 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 +39 -3
- 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
|