DhanHQ 2.6.3 → 2.7.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/AGENTS.md +23 -0
- data/ARCHITECTURE.md +2 -0
- data/CHANGELOG.md +34 -0
- data/README.md +53 -0
- data/lib/DhanHQ/concerns/order_audit.rb +69 -0
- data/lib/DhanHQ/errors.rb +2 -0
- data/lib/DhanHQ/resources/alert_orders.rb +22 -0
- data/lib/DhanHQ/resources/forever_orders.rb +10 -0
- data/lib/DhanHQ/resources/orders.rb +12 -0
- data/lib/DhanHQ/resources/pnl_exit.rb +8 -0
- data/lib/DhanHQ/resources/super_orders.rb +16 -3
- data/lib/DhanHQ/utils/network_inspector.rb +71 -0
- data/lib/DhanHQ/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df325d70c7c2e3c13493ad199ca8e11bb7f5a9df1d62a775452e4fb1eeed5842
|
|
4
|
+
data.tar.gz: 7cfde3f38fa073311ad50992ef497ba7391927b78136346e7549c135e7a4d7cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 945e4a71c15f01e33f7f05a3ed2453e7558c46d9c9bb5067a6225e68e87b4c3aac4a903ff08da384b488f9b8d36764f9f1f47103a8a8db4d5b90d1ea97e09252
|
|
7
|
+
data.tar.gz: 1b805c79a999c6604cd58d8df163c339233b09732d77c1911010dfa3d5ea32e12ae27d166b1484621338ba378acace69003328e9e6d4ec3654eb646df753c2c0
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Cursor Cloud specific instructions
|
|
4
|
+
|
|
5
|
+
This is a pure-Ruby gem (no Rails, no running servers). Development commands are documented in `CLAUDE.md`.
|
|
6
|
+
|
|
7
|
+
### Quick reference
|
|
8
|
+
|
|
9
|
+
| Task | Command |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| Install deps | `bundle install` |
|
|
12
|
+
| Tests | `bundle exec rspec` |
|
|
13
|
+
| Lint | `bundle exec rubocop` |
|
|
14
|
+
| Both | `bundle exec rake` |
|
|
15
|
+
| Single spec | `bundle exec rspec spec/path/to_spec.rb` |
|
|
16
|
+
|
|
17
|
+
### Non-obvious caveats
|
|
18
|
+
|
|
19
|
+
- **Bundler 4.x required**: The lockfile was bundled with Bundler 4.0.6. Install with `sudo gem install bundler -v 4.0.6` if missing.
|
|
20
|
+
- **`libyaml-dev` must be installed**: The `psych` gem (transitive dep) needs `yaml.h`. Without `libyaml-dev`, `bundle install` fails on native extension build.
|
|
21
|
+
- **`vendor/bundle` path**: Gems are installed to `./vendor/bundle` via `.bundle/config` to avoid needing root write access to `/var/lib/gems`.
|
|
22
|
+
- **No real API calls in tests**: All HTTP interactions are stubbed with WebMock/VCR. No `DHAN_CLIENT_ID` or `DHAN_ACCESS_TOKEN` secrets are needed to run the test suite.
|
|
23
|
+
- **No services to start**: This is a library gem. There is no web server, database, or background worker. Testing is purely `bundle exec rspec` / `bundle exec rubocop`.
|
data/ARCHITECTURE.md
CHANGED
|
@@ -48,6 +48,8 @@ This document describes the architecture of the DhanHQ v2 API client gem: layers
|
|
|
48
48
|
| `resources/` | REST wrappers | One class per API surface (Orders, Positions, MarketFeed, OptionChain, …). Set `HTTP_PATH`, `API_TYPE`; implement get/post/put/delete via BaseAPI. |
|
|
49
49
|
| `contracts/` | Request/response validation | Dry::Validation contracts (PlaceOrderContract, ModifyOrderContract, OptionChainContract, etc.). BaseContract provides shared macros (e.g. lot_size, tick_size). |
|
|
50
50
|
| `auth/` | Token lifecycle | Token generator/renewal/manager for dynamic tokens. |
|
|
51
|
+
| `concerns/` | Shared behavior | Modules included across layers (e.g. `OrderAudit` for live trading guard + audit logging, included in all order resources). |
|
|
52
|
+
| `utils/` | Utilities | Cross-cutting utilities not tied to a single layer (e.g. `NetworkInspector` for IP/hostname/env lookup used by order audit logging). |
|
|
51
53
|
| `ws/` | WebSocket | Connection, packets, decoder, market depth, orders client — isolated from REST. |
|
|
52
54
|
|
|
53
55
|
---
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
## [2.7.0] - 2026-03-17
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Order audit logging across all order types**: Every order submission (regular, super, forever/GTT, and alert orders) now emits a WARN-level structured JSON log line capturing: event type, public IPv4/IPv6, hostname, runtime environment, `security_id`, `correlation_id`, and UTC timestamp. Log output example:
|
|
6
|
+
```json
|
|
7
|
+
{"event":"DHAN_ORDER_ATTEMPT","hostname":"DESKTOP-SHUBHAM","env":"production","ipv4":"122.171.22.40","ipv6":"2401:4900:...","security_id":"11536","correlation_id":"SCALPER_7af1","timestamp":"2026-03-17T06:45:22Z"}
|
|
8
|
+
```
|
|
9
|
+
Events logged: `DHAN_ORDER_ATTEMPT`, `DHAN_ORDER_MODIFY_ATTEMPT`, `DHAN_ORDER_SLICING_ATTEMPT`, `DHAN_SUPER_ORDER_ATTEMPT`, `DHAN_SUPER_ORDER_MODIFY_ATTEMPT`, `DHAN_SUPER_ORDER_CANCEL_ATTEMPT`, `DHAN_FOREVER_ORDER_ATTEMPT`, `DHAN_FOREVER_ORDER_MODIFY_ATTEMPT`, `DHAN_FOREVER_ORDER_CANCEL_ATTEMPT`, `DHAN_ALERT_ORDER_ATTEMPT`, `DHAN_ALERT_ORDER_MODIFY_ATTEMPT`, `DHAN_ALERT_ORDER_DELETE_ATTEMPT`, `DHAN_PNL_EXIT_CONFIGURE_ATTEMPT`, `DHAN_PNL_EXIT_STOP_ATTEMPT`.
|
|
10
|
+
- **`DhanHQ::Concerns::OrderAudit`**: Shared concern providing `log_order_context` and `ensure_live_trading!` — included in `Resources::Orders`, `Resources::SuperOrders`, `Resources::ForeverOrders`, `Resources::AlertOrders`, and `Resources::PnlExit`.
|
|
11
|
+
- **`DhanHQ::Utils::NetworkInspector`**: New utility class that resolves the machine's public IPv4 (via api.ipify.org), IPv6 (via api64.ipify.org), hostname (`Socket.gethostname`), and runtime environment (`RAILS_ENV` / `RACK_ENV` / `APP_ENV`). IP lookups are memoized for the process lifetime; call `NetworkInspector.reset_cache!` to refresh.
|
|
12
|
+
- **Live trading guard**: All mutating order/trader-control calls require `ENV["LIVE_TRADING"]="true"`. Guarded methods:
|
|
13
|
+
- `Resources::Orders#create`, `#update`, `#slicing`, `#cancel`
|
|
14
|
+
- `Resources::SuperOrders#create`, `#update`, `#cancel`
|
|
15
|
+
- `Resources::ForeverOrders#create`, `#update`, `#cancel`
|
|
16
|
+
- `Resources::AlertOrders#create`, `#update`, `#delete`
|
|
17
|
+
- `Resources::PnlExit#configure`, `#stop`
|
|
18
|
+
- **`DhanHQ::LiveTradingDisabledError`**: New error class raised when the live trading guard fires.
|
|
19
|
+
- **Correlation ID prefix convention**: Recommended per-app correlation ID prefixes for instant source identification in the Dhan orderbook (e.g. `SCALPER_7af1`, `TRADER_3bc9`). See README.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`Resources::Orders`**: `#create`, `#update`, `#slicing`, `#cancel` log order context and enforce the live trading guard.
|
|
24
|
+
- **`Resources::SuperOrders`**: `#create`, `#update`, `#cancel` log order context and enforce the live trading guard.
|
|
25
|
+
- **`Resources::ForeverOrders`**: `#create`, `#update`, `#cancel` log order context and enforce the live trading guard.
|
|
26
|
+
- **`Resources::AlertOrders`**: `#create`, `#update`, `#delete` log order context and enforce the live trading guard; `#delete` added.
|
|
27
|
+
- **`Resources::PnlExit`**: `#configure` and `#stop` use `OrderAudit` (guard + audit log).
|
|
28
|
+
|
|
29
|
+
### Breaking
|
|
30
|
+
|
|
31
|
+
- **`ENV["LIVE_TRADING"]` required for all order/trader-control mutations**: Any call that creates, updates, cancels, or deletes orders (or configures/stops PnL exit) now raises `LiveTradingDisabledError` unless `ENV["LIVE_TRADING"]="true"`. Affects `Resources::Orders`, `Resources::SuperOrders`, `Resources::ForeverOrders`, `Resources::AlertOrders`, `Resources::PnlExit`, and the corresponding model wrappers. Set `LIVE_TRADING=true` in production and `LIVE_TRADING=false` (or omit) in development/test.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
1
35
|
## [2.6.3] - 2026-03-14
|
|
2
36
|
|
|
3
37
|
### Added
|
data/README.md
CHANGED
|
@@ -53,6 +53,8 @@ You could wire up Faraday and parse JSON yourself. Here's why you shouldn't:
|
|
|
53
53
|
- **Secure logging** — automatic token sanitization in all log output
|
|
54
54
|
- **Super Orders** — entry + stop-loss + target + trailing jump in one request
|
|
55
55
|
- **Instrument convenience methods** — `.ltp`, `.ohlc`, `.option_chain` directly on instruments
|
|
56
|
+
- **Order audit logging** — every order attempt logs machine, IP, environment, and correlation ID as structured JSON
|
|
57
|
+
- **Live trading guard** — prevents accidental order placement unless `ENV["LIVE_TRADING"]="true"`
|
|
56
58
|
- **Full REST coverage** — Orders, Trades, Forever Orders, Super Orders, Positions, Holdings, Funds, HistoricalData, OptionChain, MarketFeed, EDIS, Kill Switch, P&L Exit, Alert Orders, Margin Calculator
|
|
57
59
|
- **P&L Based Exit** — automatic position exit on profit/loss thresholds
|
|
58
60
|
- **Postback parser** — parse Dhan webhook payloads with `Postback.parse` and status predicates
|
|
@@ -120,6 +122,57 @@ When the API returns 401, the client retries **once** with a fresh token from yo
|
|
|
120
122
|
|
|
121
123
|
---
|
|
122
124
|
|
|
125
|
+
## Order Safety
|
|
126
|
+
|
|
127
|
+
### Live Trading Guard
|
|
128
|
+
|
|
129
|
+
Order placement (`create`, `slicing`) is blocked unless you explicitly enable it:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Production (Render, VPS, etc.)
|
|
133
|
+
LIVE_TRADING=true
|
|
134
|
+
|
|
135
|
+
# Development / Test (default — orders are blocked)
|
|
136
|
+
LIVE_TRADING=false # or simply omit
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Attempting to place an order without `LIVE_TRADING=true` raises `DhanHQ::LiveTradingDisabledError`.
|
|
140
|
+
|
|
141
|
+
### Order Audit Logging
|
|
142
|
+
|
|
143
|
+
Every order attempt (place, modify, slice) automatically logs a structured JSON line at WARN level:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"event": "DHAN_ORDER_ATTEMPT",
|
|
148
|
+
"hostname": "DESKTOP-SHUBHAM",
|
|
149
|
+
"env": "production",
|
|
150
|
+
"ipv4": "122.171.22.40",
|
|
151
|
+
"ipv6": "2401:4900:894c:8448:1da9:27f1:48e7:61be",
|
|
152
|
+
"security_id": "11536",
|
|
153
|
+
"correlation_id": "SCALPER_7af1",
|
|
154
|
+
"timestamp": "2026-03-17T06:45:22Z"
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This tells you instantly which machine, app, IP, and environment placed the order.
|
|
159
|
+
|
|
160
|
+
### Correlation ID Prefixes
|
|
161
|
+
|
|
162
|
+
Use per-app prefixes for instant source identification in the Dhan orderbook:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# algo_scalper_api
|
|
166
|
+
correlation_id: "SCALPER_#{SecureRandom.hex(4)}"
|
|
167
|
+
|
|
168
|
+
# algo_trader_api
|
|
169
|
+
correlation_id: "TRADER_#{SecureRandom.hex(4)}"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The Dhan orderbook will show `SCALPER_7af1` or `TRADER_3bc9`, making the source obvious.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
123
176
|
## REST API
|
|
124
177
|
|
|
125
178
|
### Orders — Place, Modify, Cancel
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../utils/network_inspector"
|
|
4
|
+
|
|
5
|
+
module DhanHQ
|
|
6
|
+
module Concerns
|
|
7
|
+
# Shared behavior for order audit logging and live trading safety.
|
|
8
|
+
#
|
|
9
|
+
# Include this module in any Resource that submits, modifies, or cancels
|
|
10
|
+
# orders on the Dhan API. It provides:
|
|
11
|
+
#
|
|
12
|
+
# - {#log_order_context}: emits a structured JSON log line (WARN level)
|
|
13
|
+
# capturing hostname, public IP, environment, security_id, correlation_id,
|
|
14
|
+
# and a UTC timestamp.
|
|
15
|
+
#
|
|
16
|
+
# - {#ensure_live_trading!}: raises {DhanHQ::LiveTradingDisabledError}
|
|
17
|
+
# unless +ENV["LIVE_TRADING"]+ is +"true"+, preventing accidental order
|
|
18
|
+
# placement from development machines.
|
|
19
|
+
#
|
|
20
|
+
# @example Including in a resource
|
|
21
|
+
# class MyOrders < BaseAPI
|
|
22
|
+
# include DhanHQ::Concerns::OrderAudit
|
|
23
|
+
#
|
|
24
|
+
# def create(params)
|
|
25
|
+
# ensure_live_trading!
|
|
26
|
+
# log_order_context("MY_ORDER_ATTEMPT", params)
|
|
27
|
+
# post("", params: params)
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
module OrderAudit
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Raises an error if LIVE_TRADING is not explicitly enabled.
|
|
34
|
+
# Set ENV["LIVE_TRADING"]="true" in production to allow order submission.
|
|
35
|
+
def ensure_live_trading!
|
|
36
|
+
return if ENV["LIVE_TRADING"] == "true"
|
|
37
|
+
|
|
38
|
+
raise DhanHQ::LiveTradingDisabledError,
|
|
39
|
+
"Live trading is disabled. Set ENV[\"LIVE_TRADING\"]=\"true\" to enable order placement."
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Emits a structured JSON log line with machine/network/correlation context.
|
|
43
|
+
# Uses WARN level so it appears even when INFO is silenced.
|
|
44
|
+
def log_order_context(event, params = {})
|
|
45
|
+
inspector = DhanHQ::Utils::NetworkInspector
|
|
46
|
+
entry = {
|
|
47
|
+
event: event,
|
|
48
|
+
hostname: inspector.hostname,
|
|
49
|
+
env: inspector.environment,
|
|
50
|
+
ipv4: inspector.public_ipv4,
|
|
51
|
+
ipv6: inspector.public_ipv6,
|
|
52
|
+
security_id: extract_param(params, :securityId, :security_id),
|
|
53
|
+
correlation_id: extract_param(params, :correlationId, :correlation_id),
|
|
54
|
+
order_id: extract_param(params, :orderId, :order_id),
|
|
55
|
+
timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
56
|
+
}.compact
|
|
57
|
+
|
|
58
|
+
DhanHQ.logger&.warn(JSON.generate(entry))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Extracts a value from params trying both camelCase and snake_case keys,
|
|
62
|
+
# as well as both symbol and string key types.
|
|
63
|
+
def extract_param(params, camel_key, snake_key)
|
|
64
|
+
p = params || {}
|
|
65
|
+
p[camel_key] || p[camel_key.to_s] || p[snake_key] || p[snake_key.to_s]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/DhanHQ/errors.rb
CHANGED
|
@@ -48,6 +48,8 @@ module DhanHQ
|
|
|
48
48
|
# Raised when the 25-modifications-per-order API cap would be exceeded.
|
|
49
49
|
# Count is tracked per Order instance in this process only (see Order#modify).
|
|
50
50
|
class ModificationLimitError < Error; end
|
|
51
|
+
# Raised when an order placement is attempted but ENV["LIVE_TRADING"] != "true".
|
|
52
|
+
class LiveTradingDisabledError < Error; end
|
|
51
53
|
# Raised when the API signals an issue with the requested data payload.
|
|
52
54
|
class DataError < Error; end
|
|
53
55
|
|
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../concerns/order_audit"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Resource for alert/conditional orders per API docs: /alerts/orders (GET/POST/PUT/DELETE).
|
|
6
8
|
class AlertOrders < BaseResource
|
|
9
|
+
include DhanHQ::Concerns::OrderAudit
|
|
10
|
+
|
|
7
11
|
API_TYPE = :order_api
|
|
8
12
|
HTTP_PATH = "/v2/alerts/orders"
|
|
13
|
+
|
|
14
|
+
def create(params)
|
|
15
|
+
ensure_live_trading!
|
|
16
|
+
log_order_context("DHAN_ALERT_ORDER_ATTEMPT", params)
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update(id, params)
|
|
21
|
+
ensure_live_trading!
|
|
22
|
+
log_order_context("DHAN_ALERT_ORDER_MODIFY_ATTEMPT", params.merge(alert_id: id))
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete(id)
|
|
27
|
+
ensure_live_trading!
|
|
28
|
+
log_order_context("DHAN_ALERT_ORDER_DELETE_ATTEMPT", { order_id: id })
|
|
29
|
+
super
|
|
30
|
+
end
|
|
9
31
|
end
|
|
10
32
|
end
|
|
11
33
|
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../concerns/order_audit"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Resource client for GTT/forever order management.
|
|
6
8
|
class ForeverOrders < BaseAPI
|
|
9
|
+
include DhanHQ::Concerns::OrderAudit
|
|
10
|
+
|
|
7
11
|
# Uses the trading API tier.
|
|
8
12
|
API_TYPE = :order_api
|
|
9
13
|
# Root path for forever order operations.
|
|
@@ -21,6 +25,8 @@ module DhanHQ
|
|
|
21
25
|
# @param params [Hash]
|
|
22
26
|
# @return [Hash]
|
|
23
27
|
def create(params)
|
|
28
|
+
ensure_live_trading!
|
|
29
|
+
log_order_context("DHAN_FOREVER_ORDER_ATTEMPT", params)
|
|
24
30
|
post("/orders", params: params)
|
|
25
31
|
end
|
|
26
32
|
|
|
@@ -38,6 +44,8 @@ module DhanHQ
|
|
|
38
44
|
# @param params [Hash]
|
|
39
45
|
# @return [Hash]
|
|
40
46
|
def update(order_id, params)
|
|
47
|
+
ensure_live_trading!
|
|
48
|
+
log_order_context("DHAN_FOREVER_ORDER_MODIFY_ATTEMPT", params.merge(order_id: order_id))
|
|
41
49
|
put("/orders/#{order_id}", params: params)
|
|
42
50
|
end
|
|
43
51
|
|
|
@@ -46,6 +54,8 @@ module DhanHQ
|
|
|
46
54
|
# @param order_id [String]
|
|
47
55
|
# @return [Hash]
|
|
48
56
|
def cancel(order_id)
|
|
57
|
+
ensure_live_trading!
|
|
58
|
+
log_order_context("DHAN_FOREVER_ORDER_CANCEL_ATTEMPT", { order_id: order_id })
|
|
49
59
|
delete("/orders/#{order_id}")
|
|
50
60
|
end
|
|
51
61
|
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../concerns/order_audit"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Handles order placement, modification, and cancellation
|
|
6
8
|
class Orders < BaseAPI
|
|
9
|
+
include DhanHQ::Concerns::OrderAudit
|
|
10
|
+
|
|
7
11
|
API_TYPE = :order_api
|
|
8
12
|
HTTP_PATH = "/v2/orders"
|
|
9
13
|
|
|
@@ -12,21 +16,29 @@ module DhanHQ
|
|
|
12
16
|
# --------------------------------------------------
|
|
13
17
|
|
|
14
18
|
def create(params)
|
|
19
|
+
ensure_live_trading!
|
|
20
|
+
log_order_context("DHAN_ORDER_ATTEMPT", params)
|
|
15
21
|
validate_place_order!(params)
|
|
16
22
|
post("", params: params)
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
def update(order_id, params)
|
|
26
|
+
ensure_live_trading!
|
|
27
|
+
log_order_context("DHAN_ORDER_MODIFY_ATTEMPT", params.merge(order_id: order_id))
|
|
20
28
|
validate_modify_order!(params.merge(order_id: order_id))
|
|
21
29
|
put("/#{order_id}", params: params)
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def slicing(params)
|
|
33
|
+
ensure_live_trading!
|
|
34
|
+
log_order_context("DHAN_ORDER_SLICING_ATTEMPT", params)
|
|
25
35
|
validate_place_order!(params)
|
|
26
36
|
post("/slicing", params: params)
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
def cancel(order_id)
|
|
40
|
+
ensure_live_trading!
|
|
41
|
+
log_order_context("DHAN_ORDER_CANCEL_ATTEMPT", { order_id: order_id })
|
|
30
42
|
delete("/#{order_id}")
|
|
31
43
|
end
|
|
32
44
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../concerns/order_audit"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Resource for P&L Based Exit endpoints per https://dhanhq.co/docs/v2/traders-control/
|
|
6
8
|
# POST /v2/pnlExit — configure, DELETE /v2/pnlExit — stop, GET /v2/pnlExit — status.
|
|
7
9
|
class PnlExit < BaseAPI
|
|
10
|
+
include DhanHQ::Concerns::OrderAudit
|
|
11
|
+
|
|
8
12
|
API_TYPE = :order_api
|
|
9
13
|
HTTP_PATH = "/v2/pnlExit"
|
|
10
14
|
|
|
@@ -14,6 +18,8 @@ module DhanHQ
|
|
|
14
18
|
# @param params [Hash] Request body with profitValue, lossValue, productType, enableKillSwitch.
|
|
15
19
|
# @return [Hash] API response containing pnlExitStatus and message.
|
|
16
20
|
def configure(params)
|
|
21
|
+
ensure_live_trading!
|
|
22
|
+
log_order_context("DHAN_PNL_EXIT_CONFIGURE_ATTEMPT", params)
|
|
17
23
|
post("", params: params)
|
|
18
24
|
end
|
|
19
25
|
|
|
@@ -22,6 +28,8 @@ module DhanHQ
|
|
|
22
28
|
#
|
|
23
29
|
# @return [Hash] API response containing pnlExitStatus and message.
|
|
24
30
|
def stop
|
|
31
|
+
ensure_live_trading!
|
|
32
|
+
log_order_context("DHAN_PNL_EXIT_STOP_ATTEMPT")
|
|
25
33
|
delete("")
|
|
26
34
|
end
|
|
27
35
|
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../concerns/order_audit"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Resource client for multi-leg super orders.
|
|
6
8
|
class SuperOrders < BaseAPI
|
|
9
|
+
include DhanHQ::Concerns::OrderAudit
|
|
10
|
+
|
|
7
11
|
# Super orders are executed via the trading API.
|
|
8
12
|
API_TYPE = :order_api
|
|
9
13
|
# Base path for super order endpoints.
|
|
10
14
|
HTTP_PATH = "/v2/super/orders"
|
|
11
15
|
|
|
16
|
+
SUPER_ORDER_LEGS = %w[ENTRY_LEG STOP_LOSS_LEG TARGET_LEG].freeze
|
|
17
|
+
|
|
12
18
|
# Lists all configured super orders.
|
|
13
19
|
#
|
|
14
20
|
# @return [Array<Hash>]
|
|
@@ -21,6 +27,8 @@ module DhanHQ
|
|
|
21
27
|
# @param params [Hash]
|
|
22
28
|
# @return [Hash]
|
|
23
29
|
def create(params)
|
|
30
|
+
ensure_live_trading!
|
|
31
|
+
log_order_context("DHAN_SUPER_ORDER_ATTEMPT", params)
|
|
24
32
|
post("", params: params)
|
|
25
33
|
end
|
|
26
34
|
|
|
@@ -30,11 +38,11 @@ module DhanHQ
|
|
|
30
38
|
# @param params [Hash]
|
|
31
39
|
# @return [Hash]
|
|
32
40
|
def update(order_id, params)
|
|
41
|
+
ensure_live_trading!
|
|
42
|
+
log_order_context("DHAN_SUPER_ORDER_MODIFY_ATTEMPT", params.merge(order_id: order_id))
|
|
33
43
|
put("/#{order_id}", params: params)
|
|
34
44
|
end
|
|
35
45
|
|
|
36
|
-
SUPER_ORDER_LEGS = %w[ENTRY_LEG STOP_LOSS_LEG TARGET_LEG].freeze
|
|
37
|
-
|
|
38
46
|
# Cancels a specific leg from a super order.
|
|
39
47
|
#
|
|
40
48
|
# @param order_id [String]
|
|
@@ -43,8 +51,13 @@ module DhanHQ
|
|
|
43
51
|
# @raise [DhanHQ::ValidationError] if leg_name is not a valid leg
|
|
44
52
|
def cancel(order_id, leg_name)
|
|
45
53
|
normalized = leg_name.to_s.upcase.strip
|
|
46
|
-
|
|
54
|
+
unless SUPER_ORDER_LEGS.include?(normalized)
|
|
55
|
+
raise DhanHQ::ValidationError,
|
|
56
|
+
"leg_name must be one of: #{SUPER_ORDER_LEGS.join(", ")}"
|
|
57
|
+
end
|
|
47
58
|
|
|
59
|
+
ensure_live_trading!
|
|
60
|
+
log_order_context("DHAN_SUPER_ORDER_CANCEL_ATTEMPT", { order_id: order_id, leg_name: normalized })
|
|
48
61
|
delete("/#{order_id}/#{normalized}")
|
|
49
62
|
end
|
|
50
63
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
module DhanHQ
|
|
7
|
+
module Utils
|
|
8
|
+
# Collects network and environment metadata for order audit logging.
|
|
9
|
+
#
|
|
10
|
+
# Results are memoized at class level so that repeated calls within a single
|
|
11
|
+
# process do not incur additional HTTP round-trips to the IP-lookup services.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# DhanHQ::Utils::NetworkInspector.public_ipv4 # => "122.171.22.40"
|
|
15
|
+
# DhanHQ::Utils::NetworkInspector.hostname # => "DESKTOP-SHUBHAM"
|
|
16
|
+
# DhanHQ::Utils::NetworkInspector.environment # => "production"
|
|
17
|
+
class NetworkInspector
|
|
18
|
+
IPV4_URI = URI("https://api.ipify.org")
|
|
19
|
+
IPV6_URI = URI("https://api64.ipify.org")
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# Returns the public IPv4 address of this machine.
|
|
23
|
+
# Cached after the first successful lookup. Returns "unknown" on failure.
|
|
24
|
+
#
|
|
25
|
+
# @return [String]
|
|
26
|
+
def public_ipv4
|
|
27
|
+
@public_ipv4 ||= fetch_ip(IPV4_URI)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the public IPv6 address of this machine.
|
|
31
|
+
# Cached after the first successful lookup. Returns "unknown" on failure.
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
34
|
+
def public_ipv6
|
|
35
|
+
@public_ipv6 ||= fetch_ip(IPV6_URI)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the system hostname.
|
|
39
|
+
#
|
|
40
|
+
# @return [String]
|
|
41
|
+
def hostname
|
|
42
|
+
Socket.gethostname
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the current runtime environment name.
|
|
46
|
+
# Checks RAILS_ENV, RACK_ENV, APP_ENV in order; falls back to "unknown".
|
|
47
|
+
#
|
|
48
|
+
# @return [String]
|
|
49
|
+
def environment
|
|
50
|
+
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["APP_ENV"] || "unknown"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Clears the memoized IP cache (useful in tests or when the IP may change).
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
56
|
+
def reset_cache!
|
|
57
|
+
@public_ipv4 = nil
|
|
58
|
+
@public_ipv6 = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def fetch_ip(uri)
|
|
64
|
+
Net::HTTP.get(uri).strip
|
|
65
|
+
rescue StandardError
|
|
66
|
+
"unknown"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/DhanHQ/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: DhanHQ
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shubham Taywade
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -191,6 +191,7 @@ files:
|
|
|
191
191
|
- ".rspec"
|
|
192
192
|
- ".rubocop.yml"
|
|
193
193
|
- ".rubocop_todo.yml"
|
|
194
|
+
- AGENTS.md
|
|
194
195
|
- ARCHITECTURE.md
|
|
195
196
|
- CHANGELOG.md
|
|
196
197
|
- CODE_OF_CONDUCT.md
|
|
@@ -226,6 +227,7 @@ files:
|
|
|
226
227
|
- lib/DhanHQ/auth/token_manager.rb
|
|
227
228
|
- lib/DhanHQ/auth/token_renewal.rb
|
|
228
229
|
- lib/DhanHQ/client.rb
|
|
230
|
+
- lib/DhanHQ/concerns/order_audit.rb
|
|
229
231
|
- lib/DhanHQ/configuration.rb
|
|
230
232
|
- lib/DhanHQ/constants.rb
|
|
231
233
|
- lib/DhanHQ/contracts/alert_order_contract.rb
|
|
@@ -313,6 +315,7 @@ files:
|
|
|
313
315
|
- lib/DhanHQ/resources/super_orders.rb
|
|
314
316
|
- lib/DhanHQ/resources/trader_control.rb
|
|
315
317
|
- lib/DhanHQ/resources/trades.rb
|
|
318
|
+
- lib/DhanHQ/utils/network_inspector.rb
|
|
316
319
|
- lib/DhanHQ/version.rb
|
|
317
320
|
- lib/DhanHQ/ws.rb
|
|
318
321
|
- lib/DhanHQ/ws/base_connection.rb
|