DhanHQ 2.1.0 → 2.1.3
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/README.md +17 -11
- data/docs/technical_analysis.md +143 -0
- data/lib/DhanHQ/constants.rb +4 -6
- data/lib/DhanHQ/contracts/instrument_list_contract.rb +12 -0
- data/lib/DhanHQ/helpers/request_helper.rb +5 -1
- data/lib/DhanHQ/models/instrument.rb +56 -0
- data/lib/DhanHQ/resources/instruments.rb +28 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ.rb +8 -0
- data/lib/dhanhq/analysis/helpers/bias_aggregator.rb +83 -0
- data/lib/dhanhq/analysis/helpers/moneyness_helper.rb +23 -0
- data/lib/dhanhq/analysis/multi_timeframe_analyzer.rb +230 -0
- data/lib/dhanhq/analysis/options_buying_advisor.rb +250 -0
- data/lib/dhanhq/contracts/options_buying_advisor_contract.rb +23 -0
- data/lib/ta/candles.rb +51 -0
- data/lib/ta/fetcher.rb +69 -0
- data/lib/ta/indicators.rb +168 -0
- data/lib/ta/market_calendar.rb +50 -0
- data/lib/ta/technical_analysis.rb +92 -302
- data/lib/ta.rb +7 -0
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ee56ccabd71b9f769e99d6470cf32309c4f6fa17eec5de17417b1c153ee9293
|
4
|
+
data.tar.gz: 000fcabd622e0e690f6c65bf03665b8e4b3dfc23572534d29101fca0576757be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6aeecd46b742a5cbdf74ffb4e38086be502385fda9e18bee4b6413a488b005b2a713dd089cd2d83c6628096d084d063bb6da468292b785d08f968fc9336838ac
|
7
|
+
data.tar.gz: b4fe91bca2051073b297693d1ea2789911b8b50b3a948ccf96cc7b0647a1fe824020f39ca0cbfebf53946b453253d8348df7d673eb199068d24d444453191b8d
|
data/README.md
CHANGED
@@ -44,9 +44,9 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
44
44
|
|
45
45
|
**Minimum environment variables**
|
46
46
|
|
47
|
-
| Variable
|
48
|
-
|
|
49
|
-
| `CLIENT_ID`
|
47
|
+
| Variable | Purpose |
|
48
|
+
| -------------- | ------------------------------------------------- |
|
49
|
+
| `CLIENT_ID` | Trading account client id issued by Dhan. |
|
50
50
|
| `ACCESS_TOKEN` | API access token generated from the Dhan console. |
|
51
51
|
|
52
52
|
`configure_with_env` raises if either value is missing. Load them via `dotenv`,
|
@@ -58,14 +58,14 @@ initialisation.
|
|
58
58
|
Set these variables _before_ calling `configure_with_env` when you need to
|
59
59
|
override defaults supplied by the gem:
|
60
60
|
|
61
|
-
| Variable
|
62
|
-
|
|
63
|
-
| `DHAN_LOG_LEVEL`
|
64
|
-
| `DHAN_BASE_URL`
|
65
|
-
| `DHAN_WS_VERSION`
|
66
|
-
| `DHAN_WS_ORDER_URL`
|
67
|
-
| `DHAN_WS_USER_TYPE`
|
68
|
-
| `DHAN_PARTNER_ID` / `DHAN_PARTNER_SECRET` | Required when `DHAN_WS_USER_TYPE=PARTNER`.
|
61
|
+
| Variable | When to use |
|
62
|
+
| ----------------------------------------- | ---------------------------------------------------- |
|
63
|
+
| `DHAN_LOG_LEVEL` | Adjust logger verbosity (`INFO` by default). |
|
64
|
+
| `DHAN_BASE_URL` | Point REST calls to a different API hostname. |
|
65
|
+
| `DHAN_WS_VERSION` | Pin to a specific WebSocket API version. |
|
66
|
+
| `DHAN_WS_ORDER_URL` | Override the order update WebSocket endpoint. |
|
67
|
+
| `DHAN_WS_USER_TYPE` | Switch between `SELF` and `PARTNER` streaming modes. |
|
68
|
+
| `DHAN_PARTNER_ID` / `DHAN_PARTNER_SECRET` | Required when `DHAN_WS_USER_TYPE=PARTNER`. |
|
69
69
|
|
70
70
|
### Logging
|
71
71
|
|
@@ -461,3 +461,9 @@ PRs welcome! Please include tests for new packet decoders and WS behaviors (chun
|
|
461
461
|
## License
|
462
462
|
|
463
463
|
MIT.
|
464
|
+
|
465
|
+
## Technical Analysis (Indicators + Multi-Timeframe)
|
466
|
+
|
467
|
+
See the guide for computing indicators and aggregating cross-timeframe bias:
|
468
|
+
|
469
|
+
- docs/technical_analysis.md
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# Technical Analysis Guide
|
2
|
+
|
3
|
+
This guide explains how to use the technical analysis modules bundled with this gem: fetching historical OHLC, computing indicators, and producing multi-timeframe summaries.
|
4
|
+
|
5
|
+
## Modules Overview
|
6
|
+
|
7
|
+
- `TA::TechnicalAnalysis`: Fetches intraday OHLC (1/5/15/25/60) from Dhan APIs with throttling/backoff, computes RSI/MACD/ADX/ATR, and returns a structured indicators hash.
|
8
|
+
- `TA::Indicators`: Adapters for `ruby-technical-analysis` and `technical-analysis` gems, including safe fallbacks.
|
9
|
+
- `TA::Candles`: Utilities for converting API series to candles and resampling (used for offline data only).
|
10
|
+
- `TA::Fetcher`: Handles API calls, 90-day windowing, throttling, and retries.
|
11
|
+
- `DhanHQ::Analysis::MultiTimeframeAnalyzer`: Consumes the indicator hash and outputs a consolidated bias summary across timeframes.
|
12
|
+
|
13
|
+
## Prerequisites
|
14
|
+
|
15
|
+
- Environment variables set: `CLIENT_ID`, `ACCESS_TOKEN`
|
16
|
+
- Optional indicator gems:
|
17
|
+
- `gem install ruby-technical-analysis technical-analysis`
|
18
|
+
|
19
|
+
## Quick Start: Compute Indicators
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require "ta"
|
23
|
+
|
24
|
+
DhanHQ.configure_with_env
|
25
|
+
|
26
|
+
ta = TA::TechnicalAnalysis.new(throttle_seconds: 2.5, max_retries: 3)
|
27
|
+
indicators = ta.compute(
|
28
|
+
exchange_segment: "NSE_EQ",
|
29
|
+
instrument: "EQUITY",
|
30
|
+
security_id: "1333",
|
31
|
+
intervals: [1, 5, 15, 25, 60] # each fetched directly from API
|
32
|
+
)
|
33
|
+
```
|
34
|
+
|
35
|
+
Output structure:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
{
|
39
|
+
meta: { exchange_segment: "...", instrument: "...", security_id: "...", from_date: "YYYY-MM-DD", to_date: "YYYY-MM-DD" },
|
40
|
+
indicators: {
|
41
|
+
m1: { rsi: Float|nil, adx: Float|nil, atr: Float|nil, macd: { macd: Float|nil, signal: Float|nil, hist: Float|nil } },
|
42
|
+
m5: { ... },
|
43
|
+
m15: { ... },
|
44
|
+
m25: { ... },
|
45
|
+
m60: { ... }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
```
|
49
|
+
|
50
|
+
Notes:
|
51
|
+
- `to_date` defaults to today-or-last-trading-day via `TA::MarketCalendar`.
|
52
|
+
- If `days_back` is not provided, the class auto-selects a sufficient lookback (trading days) per the selected intervals and indicator periods (max of [2×ADX, MACD slow, RSI+1, ATR+1]).
|
53
|
+
- Requests are throttled with jitter; rate-limit errors trigger exponential backoff.
|
54
|
+
|
55
|
+
## Offline Input (JSON OHLC)
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
raw = JSON.parse(File.read("ohlc.json"))
|
59
|
+
indicators = TA::TechnicalAnalysis.new.compute_from_file(
|
60
|
+
path: "ohlc.json", base_interval: 1, intervals: [1,5,15,25,60]
|
61
|
+
)
|
62
|
+
```
|
63
|
+
|
64
|
+
## Analyze Multi-Timeframe Bias
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require "DhanHQ"
|
68
|
+
|
69
|
+
analyzer = DhanHQ::Analysis::MultiTimeframeAnalyzer.new(data: indicators)
|
70
|
+
summary = analyzer.call
|
71
|
+
```
|
72
|
+
|
73
|
+
Example summary:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
{
|
77
|
+
meta: { security_id: "1333", instrument: "EQUITY", exchange_segment: "NSE_EQ" },
|
78
|
+
summary: {
|
79
|
+
bias: :bullish, # :bullish | :bearish | :neutral
|
80
|
+
setup: :buy_on_dip, # :buy_on_dip | :sell_on_rise | :range_trade
|
81
|
+
confidence: 0.78, # 0.0..1.0 weighted across timeframes
|
82
|
+
rationale: {
|
83
|
+
rsi: "Upward momentum across M5–M60",
|
84
|
+
macd: "MACD bullish signals dominant",
|
85
|
+
adx: "Strong higher timeframe trend",
|
86
|
+
atr: "Volatility expansion"
|
87
|
+
},
|
88
|
+
trend_strength: {
|
89
|
+
short_term: :weak_bullish,
|
90
|
+
medium_term: :neutral_to_bullish,
|
91
|
+
long_term: :strong_bullish
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
```
|
96
|
+
|
97
|
+
## CLI Script
|
98
|
+
|
99
|
+
A convenience script exists at `bin/ta_strategy.rb` to compute indicators and print JSON. Example:
|
100
|
+
|
101
|
+
```bash
|
102
|
+
./bin/ta_strategy.rb --segment NSE_EQ --instrument EQUITY --security-id 1333 \
|
103
|
+
--from 2025-10-06 --to 2025-10-07 --debug
|
104
|
+
```
|
105
|
+
|
106
|
+
Options:
|
107
|
+
- `--print-creds` to verify env
|
108
|
+
- `--data-file` to compute from JSON instead of API
|
109
|
+
- `--interval` (with `--data-file`) to specify base file interval
|
110
|
+
- `--rsi`, `--atr`, `--adx`, `--macd` to tune periods
|
111
|
+
|
112
|
+
## Options Buying Advisor CLI
|
113
|
+
|
114
|
+
Use `bin/options_advisor.rb` to compute indicators, summarize multi-TF bias, and produce a single index options-buying recommendation (CE/PE). If you do not provide `--spot`, the script fetches spot via MarketFeed LTP automatically.
|
115
|
+
|
116
|
+
Examples:
|
117
|
+
|
118
|
+
```bash
|
119
|
+
# Auto-fetch spot via MarketFeed.ltp and chain via OptionChain model
|
120
|
+
./bin/options_advisor.rb --segment IDX_I --instrument INDEX --security-id 13 --symbol NIFTY
|
121
|
+
|
122
|
+
# Provide pre-fetched option chain from file (JSON array of strikes)
|
123
|
+
./bin/options_advisor.rb --segment IDX_I --instrument INDEX --security-id 13 --symbol NIFTY \
|
124
|
+
--chain-file ./chain.json
|
125
|
+
|
126
|
+
# Override spot explicitly
|
127
|
+
./bin/options_advisor.rb --segment IDX_I --instrument INDEX --security-id 13 --symbol NIFTY --spot 24890
|
128
|
+
|
129
|
+
# Verbose debug logging (prints steps to STDERR; JSON output unchanged)
|
130
|
+
./bin/options_advisor.rb --segment IDX_I --instrument INDEX --security-id 13 --symbol NIFTY --debug
|
131
|
+
```
|
132
|
+
|
133
|
+
Behavior:
|
134
|
+
- Spot: when `--spot` is omitted, the script calls `DhanHQ::Models::MarketFeed.ltp({ SEG => [security_id] })` and reads `data[SEG][security_id]["last_price"]`.
|
135
|
+
- Option chain: when `--chain-file` is omitted, the advisor fetches via `DhanHQ::Models::OptionChain` (nearest expiry), transforming OC into an internal array of strikes with CE/PE legs.
|
136
|
+
- Debugging: with `--debug`, the script logs options, spot resolution, indicators meta, missing fields per timeframe, analyzer summary, and advisor output to STDERR.
|
137
|
+
|
138
|
+
## Best Practices & Tips
|
139
|
+
|
140
|
+
- Keep intervals minimal per run to reduce rate limits; increase `throttle_seconds` if needed.
|
141
|
+
- For higher intervals (e.g., 60m), ensure adequate `days_back` (auto-calculation is enabled by default).
|
142
|
+
- The analyzer is heuristic; adjust weights or thresholds as your strategy matures.
|
143
|
+
- See the advisor helpers under `lib/dhanhq/analysis/helpers/` to customize bias and moneyness rules.
|
data/lib/DhanHQ/constants.rb
CHANGED
@@ -131,13 +131,11 @@ module DhanHQ
|
|
131
131
|
# Download URL for the detailed instrument master CSV.
|
132
132
|
DETAILED_CSV_URL = "https://images.dhan.co/api-data/api-scrip-master-detailed.csv"
|
133
133
|
|
134
|
-
# API
|
135
|
-
|
136
|
-
/v2/marketfeed/
|
137
|
-
/v2/marketfeed/ohlc
|
138
|
-
/v2/marketfeed/quote
|
134
|
+
# API route prefixes that require a `client-id` header in addition to the access token.
|
135
|
+
DATA_API_PREFIXES = %w[
|
136
|
+
/v2/marketfeed/
|
139
137
|
/v2/optionchain
|
140
|
-
/v2/
|
138
|
+
/v2/instrument/
|
141
139
|
].freeze
|
142
140
|
|
143
141
|
# Mapping of DhanHQ error codes to SDK error classes for consistent exception handling.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DhanHQ
|
4
|
+
module Contracts
|
5
|
+
# Validates the exchange segment param for instrument list endpoint
|
6
|
+
class InstrumentListContract < BaseContract
|
7
|
+
params do
|
8
|
+
required(:exchange_segment).filled(:string, included_in?: EXCHANGE_SEGMENTS)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -26,6 +26,9 @@ module DhanHQ
|
|
26
26
|
# @param path [String] The API endpoint path.
|
27
27
|
# @return [Hash] The request headers.
|
28
28
|
def build_headers(path)
|
29
|
+
# Public CSV endpoint for segment-wise instruments requires no auth
|
30
|
+
return { "Accept" => "text/csv" } if path.start_with?("/v2/instrument/")
|
31
|
+
|
29
32
|
headers = {
|
30
33
|
"Content-Type" => "application/json",
|
31
34
|
"Accept" => "application/json",
|
@@ -43,7 +46,8 @@ module DhanHQ
|
|
43
46
|
# @param path [String] The API endpoint path.
|
44
47
|
# @return [Boolean] True if the path belongs to a DATA API.
|
45
48
|
def data_api?(path)
|
46
|
-
DhanHQ::Constants::
|
49
|
+
prefixes = DhanHQ::Constants::DATA_API_PREFIXES
|
50
|
+
prefixes.any? { |p| path.start_with?(p) }
|
47
51
|
end
|
48
52
|
|
49
53
|
# Prepares the request payload based on the HTTP method.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../contracts/instrument_list_contract"
|
4
|
+
|
5
|
+
module DhanHQ
|
6
|
+
module Models
|
7
|
+
# Model wrapper for fetching instruments by exchange segment.
|
8
|
+
class Instrument < BaseModel
|
9
|
+
attributes :security_id, :symbol_name, :display_name, :exchange_segment, :instrument, :series,
|
10
|
+
:lot_size, :tick_size, :expiry_date, :strike_price, :option_type
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @return [DhanHQ::Resources::Instruments]
|
14
|
+
def resource
|
15
|
+
@resource ||= DhanHQ::Resources::Instruments.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieve instruments for a given segment, returning an array of models.
|
19
|
+
# @param exchange_segment [String]
|
20
|
+
# @return [Array<Instrument>]
|
21
|
+
def by_segment(exchange_segment)
|
22
|
+
validate_params!({ exchange_segment: exchange_segment }, DhanHQ::Contracts::InstrumentListContract)
|
23
|
+
|
24
|
+
csv_text = resource.by_segment(exchange_segment)
|
25
|
+
return [] unless csv_text.is_a?(String) && !csv_text.empty?
|
26
|
+
|
27
|
+
require "csv"
|
28
|
+
rows = CSV.parse(csv_text, headers: true)
|
29
|
+
rows.map { |r| new(normalize_csv_row(r), skip_validation: true) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def normalize_csv_row(row)
|
33
|
+
{
|
34
|
+
security_id: row["SECURITY_ID"].to_s,
|
35
|
+
symbol_name: row["SYMBOL_NAME"],
|
36
|
+
display_name: row["DISPLAY_NAME"],
|
37
|
+
exchange_segment: row["EXCH_ID"],
|
38
|
+
instrument: row["INSTRUMENT"],
|
39
|
+
series: row["SERIES"],
|
40
|
+
lot_size: row["LOT_SIZE"]&.to_f,
|
41
|
+
tick_size: row["TICK_SIZE"]&.to_f,
|
42
|
+
expiry_date: row["SM_EXPIRY_DATE"],
|
43
|
+
strike_price: row["STRIKE_PRICE"]&.to_f,
|
44
|
+
option_type: row["OPTION_TYPE"]
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def validation_contract
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DhanHQ
|
4
|
+
module Resources
|
5
|
+
# Resource client for fetching segment-wise instrument lists.
|
6
|
+
class Instruments < BaseAPI
|
7
|
+
# Instruments are served via the non-trading/data tier
|
8
|
+
API_TYPE = :data_api
|
9
|
+
# Base path for instruments endpoint
|
10
|
+
HTTP_PATH = "/v2/instrument"
|
11
|
+
|
12
|
+
# Fetch instruments for a given exchange segment.
|
13
|
+
# Returns CSV text; the client parses to Array<Hash> upstream if needed.
|
14
|
+
#
|
15
|
+
# @param exchange_segment [String] e.g. "NSE_EQ", "NSE_FNO", "IDX_I"
|
16
|
+
# @return [String] CSV content
|
17
|
+
def by_segment(exchange_segment)
|
18
|
+
path = "#{HTTP_PATH}/#{exchange_segment}"
|
19
|
+
resp = client.connection.get(path)
|
20
|
+
if resp.status.between?(300, 399) && resp.headers["location"]
|
21
|
+
redirect_url = resp.headers["location"]
|
22
|
+
return Faraday.get(redirect_url).body
|
23
|
+
end
|
24
|
+
resp.body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/DhanHQ/version.rb
CHANGED
data/lib/DhanHQ.rb
CHANGED
@@ -44,6 +44,7 @@ require_relative "DhanHQ/resources/trades"
|
|
44
44
|
require_relative "DhanHQ/resources/historical_data"
|
45
45
|
require_relative "DhanHQ/resources/margin_calculator"
|
46
46
|
require_relative "DhanHQ/resources/market_feed"
|
47
|
+
require_relative "DhanHQ/resources/instruments"
|
47
48
|
require_relative "DhanHQ/resources/edis"
|
48
49
|
require_relative "DhanHQ/resources/kill_switch"
|
49
50
|
require_relative "DhanHQ/resources/profile"
|
@@ -56,6 +57,7 @@ require_relative "DhanHQ/models/forever_order"
|
|
56
57
|
require_relative "DhanHQ/models/super_order"
|
57
58
|
require_relative "DhanHQ/models/historical_data"
|
58
59
|
require_relative "DhanHQ/models/market_feed"
|
60
|
+
require_relative "DhanHQ/models/instrument"
|
59
61
|
require_relative "DhanHQ/models/position"
|
60
62
|
require_relative "DhanHQ/models/holding"
|
61
63
|
require_relative "DhanHQ/models/ledger_entry"
|
@@ -68,6 +70,12 @@ require_relative "DhanHQ/models/profile"
|
|
68
70
|
require_relative "DhanHQ/constants"
|
69
71
|
require_relative "DhanHQ/ws"
|
70
72
|
require_relative "DhanHQ/ws/singleton_lock"
|
73
|
+
require_relative "ta"
|
74
|
+
require_relative "dhanhq/analysis/multi_timeframe_analyzer"
|
75
|
+
require_relative "dhanhq/analysis/helpers/bias_aggregator"
|
76
|
+
require_relative "dhanhq/analysis/helpers/moneyness_helper"
|
77
|
+
require_relative "dhanhq/contracts/options_buying_advisor_contract"
|
78
|
+
require_relative "dhanhq/analysis/options_buying_advisor"
|
71
79
|
|
72
80
|
# The top-level module for the DhanHQ client library.
|
73
81
|
#
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DhanHQ
|
4
|
+
module Analysis
|
5
|
+
class BiasAggregator
|
6
|
+
DEFAULT_WEIGHTS = { m1: 0.1, m5: 0.2, m15: 0.25, m25: 0.15, m60: 0.3 }.freeze
|
7
|
+
|
8
|
+
def initialize(indicators, config = {})
|
9
|
+
@indicators = indicators || {}
|
10
|
+
@weights = config[:timeframe_weights] || DEFAULT_WEIGHTS
|
11
|
+
@min_adx = (config[:min_adx_for_trend] || 22).to_f
|
12
|
+
@strong_adx = (config[:strong_adx] || 35).to_f
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
score = 0.0
|
17
|
+
wsum = 0.0
|
18
|
+
refs = []
|
19
|
+
notes = []
|
20
|
+
|
21
|
+
@weights.each do |tf, w|
|
22
|
+
next unless @indicators[tf]
|
23
|
+
|
24
|
+
s = score_tf(@indicators[tf])
|
25
|
+
next if s.nil?
|
26
|
+
|
27
|
+
score += s * w
|
28
|
+
wsum += w
|
29
|
+
refs << tf
|
30
|
+
end
|
31
|
+
|
32
|
+
avg = wsum.zero? ? 0.5 : (score / wsum)
|
33
|
+
bias = if avg > 0.55
|
34
|
+
:bullish
|
35
|
+
elsif avg < 0.45
|
36
|
+
:bearish
|
37
|
+
else
|
38
|
+
:neutral
|
39
|
+
end
|
40
|
+
|
41
|
+
{ bias: bias, confidence: avg.round(2), refs: refs, notes: notes }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def score_tf(val)
|
47
|
+
rsi = val[:rsi]
|
48
|
+
macd = val[:macd] || {}
|
49
|
+
hist = macd[:hist]
|
50
|
+
adx = val[:adx]
|
51
|
+
|
52
|
+
rsi_component = case rsi
|
53
|
+
when nil then 0.5
|
54
|
+
else
|
55
|
+
return 0.65 if rsi >= 55
|
56
|
+
return 0.35 if rsi <= 45
|
57
|
+
|
58
|
+
0.5
|
59
|
+
end
|
60
|
+
|
61
|
+
macd_component = case hist
|
62
|
+
when nil then 0.5
|
63
|
+
else
|
64
|
+
hist >= 0 ? 0.6 : 0.4
|
65
|
+
end
|
66
|
+
|
67
|
+
adx_component = case adx
|
68
|
+
when nil then 0.5
|
69
|
+
else
|
70
|
+
if adx >= @strong_adx
|
71
|
+
0.65
|
72
|
+
elsif adx >= @min_adx
|
73
|
+
0.55
|
74
|
+
else
|
75
|
+
0.45
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
(rsi_component * 0.4) + (macd_component * 0.3) + (adx_component * 0.3)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DhanHQ
|
4
|
+
module Analysis
|
5
|
+
module MoneynessHelper
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def pick_moneyness(indicators:, min_adx:, strong_adx:, bias: nil)
|
9
|
+
# Mark bias as intentionally observed for future rules
|
10
|
+
bias&.to_sym
|
11
|
+
|
12
|
+
m60 = indicators[:m60] || {}
|
13
|
+
adx = m60[:adx].to_f
|
14
|
+
rsi = m60[:rsi].to_f
|
15
|
+
|
16
|
+
return :atm if adx < min_adx
|
17
|
+
return :otm if adx >= strong_adx && rsi >= 60
|
18
|
+
|
19
|
+
:atm
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|