indicator_hub 0.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +551 -0
- data/Rakefile +12 -0
- data/exe/indicator_hub +4 -0
- data/lib/indicator_hub/calculation_helpers.rb +196 -0
- data/lib/indicator_hub/indicators/adi.rb +37 -0
- data/lib/indicator_hub/indicators/adtv.rb +27 -0
- data/lib/indicator_hub/indicators/adx.rb +102 -0
- data/lib/indicator_hub/indicators/ao.rb +39 -0
- data/lib/indicator_hub/indicators/atr.rb +45 -0
- data/lib/indicator_hub/indicators/base_indicator.rb +68 -0
- data/lib/indicator_hub/indicators/bb.rb +44 -0
- data/lib/indicator_hub/indicators/cci.rb +41 -0
- data/lib/indicator_hub/indicators/cmf.rb +54 -0
- data/lib/indicator_hub/indicators/cmo.rb +49 -0
- data/lib/indicator_hub/indicators/cr.rb +26 -0
- data/lib/indicator_hub/indicators/dc.rb +40 -0
- data/lib/indicator_hub/indicators/dlr.rb +27 -0
- data/lib/indicator_hub/indicators/dpo.rb +32 -0
- data/lib/indicator_hub/indicators/dr.rb +27 -0
- data/lib/indicator_hub/indicators/ema.rb +40 -0
- data/lib/indicator_hub/indicators/envelopes_ema.rb +36 -0
- data/lib/indicator_hub/indicators/eom.rb +48 -0
- data/lib/indicator_hub/indicators/fi.rb +45 -0
- data/lib/indicator_hub/indicators/ichimoku.rb +76 -0
- data/lib/indicator_hub/indicators/imi.rb +48 -0
- data/lib/indicator_hub/indicators/kc.rb +46 -0
- data/lib/indicator_hub/indicators/kst.rb +82 -0
- data/lib/indicator_hub/indicators/macd.rb +46 -0
- data/lib/indicator_hub/indicators/mfi.rb +62 -0
- data/lib/indicator_hub/indicators/mi.rb +81 -0
- data/lib/indicator_hub/indicators/nvi.rb +42 -0
- data/lib/indicator_hub/indicators/obv.rb +41 -0
- data/lib/indicator_hub/indicators/obv_mean.rb +42 -0
- data/lib/indicator_hub/indicators/pivot_points.rb +44 -0
- data/lib/indicator_hub/indicators/price_channel.rb +38 -0
- data/lib/indicator_hub/indicators/qstick.rb +36 -0
- data/lib/indicator_hub/indicators/rmi.rb +48 -0
- data/lib/indicator_hub/indicators/roc.rb +37 -0
- data/lib/indicator_hub/indicators/rsi.rb +67 -0
- data/lib/indicator_hub/indicators/sma.rb +32 -0
- data/lib/indicator_hub/indicators/so.rb +76 -0
- data/lib/indicator_hub/indicators/trix.rb +53 -0
- data/lib/indicator_hub/indicators/tsi.rb +67 -0
- data/lib/indicator_hub/indicators/uo.rb +67 -0
- data/lib/indicator_hub/indicators/vi.rb +54 -0
- data/lib/indicator_hub/indicators/volume_oscillator.rb +55 -0
- data/lib/indicator_hub/indicators/vpt.rb +35 -0
- data/lib/indicator_hub/indicators/vwap.rb +33 -0
- data/lib/indicator_hub/indicators/wilders_smoothing.rb +36 -0
- data/lib/indicator_hub/indicators/wma.rb +36 -0
- data/lib/indicator_hub/indicators/wr.rb +36 -0
- data/lib/indicator_hub/series.rb +79 -0
- data/lib/indicator_hub/talib_adapter.rb +23 -0
- data/lib/indicator_hub/validation.rb +49 -0
- data/lib/indicator_hub/version.rb +6 -0
- data/lib/indicator_hub.rb +485 -0
- data/sig/indicator_hub.rbs +4 -0
- metadata +112 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 655be8ee3ffcbc7fdcdb6f4b66c56c78bde2e414de74d146550b67666f92f4c5
|
|
4
|
+
data.tar.gz: 3be616665ae69e54d12c0a3daf3349d980a42999ba0d9560c385720f9aec47cc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e260e9516c1f002435fea29bed6611f53648aea12b98b863bac06923c9976c86f351441508f9908cd48c7f392f42fb7b5cf5c5c98972d1849b688a27b34b006f
|
|
7
|
+
data.tar.gz: 12cf1b88a3852d334b0f4b47f925ee6bbb4215e2b488cdc20c2ccdf41d3a78e1e43678c25ecbb248e183b6877e1847789375dc5473cc54a5f3fd4df63aae2bac
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"indicator_hub" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["shubhamtaywade82@gmail.com"](mailto:"shubhamtaywade82@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shubham Taywade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
# IndicatorHub
|
|
2
|
+
|
|
3
|
+
IndicatorHub is a unified, clean, and idiomatic Ruby gem for technical analysis. It aggregates and optimizes the core math from multiple popular technical analysis gems into a single, high-performance, pure Ruby library.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Unified API**: Calculate SMA, EMA, RSI, MACD, and Bollinger Bands through a single entry point.
|
|
8
|
+
- **Data Agnostic**: Supports simple price arrays or complex OHLCV hash data.
|
|
9
|
+
- **Pure Ruby**: Zero dependencies by default (math implementations are self-contained).
|
|
10
|
+
- **Optional Performance**: Can optionally leverage `talib_ffi` for high-performance calculations if the C-library is available on the system.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Add this line to your application's Gemfile:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem 'indicator_hub'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
And then execute:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
$ bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or install it yourself:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
$ gem install indicator_hub
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage Guide
|
|
33
|
+
|
|
34
|
+
IndicatorHub works in two common modes:
|
|
35
|
+
|
|
36
|
+
- Standalone Ruby scripts, services, and CLIs
|
|
37
|
+
- Rails applications using ActiveRecord models or service objects
|
|
38
|
+
|
|
39
|
+
The API accepts either:
|
|
40
|
+
|
|
41
|
+
- A simple numeric series like `[100.5, 101.2, 99.8]`
|
|
42
|
+
- An OHLCV series like `[{ timestamp:, open:, high:, low:, close:, volume: }]`
|
|
43
|
+
|
|
44
|
+
Most moving averages and momentum indicators work with a numeric series. Indicators that depend on high, low, or volume expect OHLCV hashes.
|
|
45
|
+
|
|
46
|
+
## Using In Standalone Ruby Apps
|
|
47
|
+
|
|
48
|
+
Install the gem:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
gem install indicator_hub
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then require and use it:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
require "indicator_hub"
|
|
58
|
+
|
|
59
|
+
closes = [100.0, 101.5, 102.2, 101.8, 103.4, 104.1]
|
|
60
|
+
|
|
61
|
+
sma = IndicatorHub.sma(closes, period: 3)
|
|
62
|
+
ema = IndicatorHub.ema(closes, period: 3)
|
|
63
|
+
rsi = IndicatorHub.rsi(closes, period: 5)
|
|
64
|
+
|
|
65
|
+
puts sma.inspect
|
|
66
|
+
puts ema.inspect
|
|
67
|
+
puts rsi.inspect
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Example with OHLCV candles:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
require "indicator_hub"
|
|
74
|
+
|
|
75
|
+
candles = [
|
|
76
|
+
{ timestamp: 1704067200, open: 100.0, high: 103.0, low: 99.0, close: 102.0, volume: 1200.0 },
|
|
77
|
+
{ timestamp: 1704153600, open: 102.0, high: 104.0, low: 101.0, close: 103.0, volume: 1500.0 },
|
|
78
|
+
{ timestamp: 1704240000, open: 103.0, high: 105.0, low: 100.0, close: 101.0, volume: 1700.0 }
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
atr = IndicatorHub.atr(candles, period: 2)
|
|
82
|
+
adx = IndicatorHub.adx(candles, period: 2)
|
|
83
|
+
vwap = IndicatorHub.vwap(candles)
|
|
84
|
+
|
|
85
|
+
puts atr.inspect
|
|
86
|
+
puts adx.inspect
|
|
87
|
+
puts vwap.inspect
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Using In Rails Apps
|
|
91
|
+
|
|
92
|
+
Add the gem to your `Gemfile`:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
gem "indicator_hub"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then run:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
bundle install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
In Rails, you usually call IndicatorHub from:
|
|
105
|
+
|
|
106
|
+
- a model method
|
|
107
|
+
- a query/service object
|
|
108
|
+
- a background job
|
|
109
|
+
- a controller or API serializer
|
|
110
|
+
|
|
111
|
+
Example `PriceBar` model usage:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# app/models/price_bar.rb
|
|
115
|
+
class PriceBar < ApplicationRecord
|
|
116
|
+
scope :chronological, -> { order(:traded_at) }
|
|
117
|
+
|
|
118
|
+
def self.to_indicator_series(limit: 200)
|
|
119
|
+
chronological.limit(limit).map do |bar|
|
|
120
|
+
{
|
|
121
|
+
timestamp: bar.traded_at.to_i,
|
|
122
|
+
open: bar.open,
|
|
123
|
+
high: bar.high,
|
|
124
|
+
low: bar.low,
|
|
125
|
+
close: bar.close,
|
|
126
|
+
volume: bar.volume
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Example service object:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
# app/services/indicator_snapshot.rb
|
|
137
|
+
class IndicatorSnapshot
|
|
138
|
+
def initialize(scope = PriceBar.all)
|
|
139
|
+
@scope = scope
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def call(limit: 200)
|
|
143
|
+
candles = @scope.order(:traded_at).limit(limit).map do |bar|
|
|
144
|
+
{
|
|
145
|
+
open: bar.open,
|
|
146
|
+
high: bar.high,
|
|
147
|
+
low: bar.low,
|
|
148
|
+
close: bar.close,
|
|
149
|
+
volume: bar.volume
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
{
|
|
154
|
+
sma_20: IndicatorHub.sma(candles, period: 20, field: :close).last,
|
|
155
|
+
ema_20: IndicatorHub.ema(candles, period: 20, field: :close).last,
|
|
156
|
+
rsi_14: IndicatorHub.rsi(candles, period: 14, field: :close).last,
|
|
157
|
+
macd: IndicatorHub.macd(candles).last,
|
|
158
|
+
atr_14: IndicatorHub.atr(candles, period: 14).last
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Example from Rails console:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
bars = PriceBar.order(:traded_at).last(100).map do |bar|
|
|
168
|
+
{
|
|
169
|
+
open: bar.open,
|
|
170
|
+
high: bar.high,
|
|
171
|
+
low: bar.low,
|
|
172
|
+
close: bar.close,
|
|
173
|
+
volume: bar.volume
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
IndicatorHub.bb(bars, period: 20, field: :close).last
|
|
178
|
+
IndicatorHub.macd(bars, field: :close).last
|
|
179
|
+
IndicatorHub.vwap(bars).last
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Data Formats
|
|
183
|
+
|
|
184
|
+
### Numeric Series
|
|
185
|
+
|
|
186
|
+
Use this for indicators based on one field, usually close:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
closes = [100.0, 101.2, 102.8, 103.1]
|
|
190
|
+
|
|
191
|
+
IndicatorHub.sma(closes, period: 3)
|
|
192
|
+
IndicatorHub.ema(closes, period: 3)
|
|
193
|
+
IndicatorHub.rsi(closes, period: 14)
|
|
194
|
+
IndicatorHub.macd(closes)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Hash Series With `field:`
|
|
198
|
+
|
|
199
|
+
If you already have hashes and want a single-field indicator:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
rows = [
|
|
203
|
+
{ open: 100.0, close: 101.0 },
|
|
204
|
+
{ open: 101.0, close: 103.0 },
|
|
205
|
+
{ open: 103.0, close: 102.0 }
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
IndicatorHub.sma(rows, period: 2, field: :close)
|
|
209
|
+
IndicatorHub.ema(rows, period: 2, field: :open)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### OHLCV Series
|
|
213
|
+
|
|
214
|
+
Use this for indicators that need candle ranges or volume:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
rows = [
|
|
218
|
+
{ timestamp: 1704067200, open: 100.0, high: 103.0, low: 99.0, close: 101.0, volume: 1000.0 },
|
|
219
|
+
{ timestamp: 1704153600, open: 101.0, high: 104.0, low: 100.0, close: 103.0, volume: 1200.0 }
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
IndicatorHub.atr(rows, period: 14)
|
|
223
|
+
IndicatorHub.adx(rows, period: 14)
|
|
224
|
+
IndicatorHub.mfi(rows, period: 14)
|
|
225
|
+
IndicatorHub.vwap(rows)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`timestamp` is recommended for real market data feeds. The indicator methods only use OHLCV values for calculations, so extra keys like `timestamp`, `symbol`, or `open_interest` are safe to keep in your source payloads.
|
|
229
|
+
|
|
230
|
+
## Provider Payload Examples
|
|
231
|
+
|
|
232
|
+
### Delta Exchange Response
|
|
233
|
+
|
|
234
|
+
The `delta_exchange` gem returns the parsed API response envelope from `/v2/history/candles`. In practice, you extract `result` and normalize `time` to `timestamp` if you want a consistent candle shape in your app:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
payload = {
|
|
238
|
+
"success" => true,
|
|
239
|
+
"result" => [
|
|
240
|
+
{
|
|
241
|
+
"time" => 1704067200,
|
|
242
|
+
"open" => 100.0,
|
|
243
|
+
"high" => 103.0,
|
|
244
|
+
"low" => 99.0,
|
|
245
|
+
"close" => 101.0,
|
|
246
|
+
"volume" => 1200.0
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
candles = payload["result"].map do |row|
|
|
252
|
+
{
|
|
253
|
+
timestamp: row["time"],
|
|
254
|
+
open: row["open"],
|
|
255
|
+
high: row["high"],
|
|
256
|
+
low: row["low"],
|
|
257
|
+
close: row["close"],
|
|
258
|
+
volume: row["volume"]
|
|
259
|
+
}
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
IndicatorHub.atr(candles, period: 14)
|
|
263
|
+
IndicatorHub.rsi(candles, period: 14, field: :close)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### DhanHQ Client Response
|
|
267
|
+
|
|
268
|
+
The `dhanhq-client` gem already normalizes historical responses into an array of candle hashes in `DhanHQ::Models::HistoricalData.daily` and `DhanHQ::Models::HistoricalData.intraday`.
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
candles = DhanHQ::Models::HistoricalData.intraday(
|
|
272
|
+
security_id: "13",
|
|
273
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::IDX_I,
|
|
274
|
+
instrument: DhanHQ::Constants::InstrumentType::INDEX,
|
|
275
|
+
interval: "5",
|
|
276
|
+
from_date: "2024-08-14",
|
|
277
|
+
to_date: "2024-08-14"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# candles.first
|
|
281
|
+
# => {
|
|
282
|
+
# timestamp: 2024-08-14 09:15:00 +0530,
|
|
283
|
+
# open: 3750.0,
|
|
284
|
+
# high: 3757.9,
|
|
285
|
+
# low: 3746.1,
|
|
286
|
+
# close: 3751.25,
|
|
287
|
+
# volume: 53629
|
|
288
|
+
# }
|
|
289
|
+
|
|
290
|
+
IndicatorHub.macd(candles, field: :close)
|
|
291
|
+
IndicatorHub.vwap(candles)
|
|
292
|
+
IndicatorHub.obv(candles)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
If you are working with Dhan's raw API payload before `dhanhq-client` normalizes it, then yes, you would first zip the parallel arrays into candle hashes.
|
|
296
|
+
|
|
297
|
+
### Normalizing In One Helper
|
|
298
|
+
|
|
299
|
+
For app code, it is usually better to normalize provider responses in one place:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
module CandleNormalizer
|
|
303
|
+
module_function
|
|
304
|
+
|
|
305
|
+
def from_delta(payload)
|
|
306
|
+
payload.fetch("result", []).map do |row|
|
|
307
|
+
{
|
|
308
|
+
timestamp: row["time"],
|
|
309
|
+
open: row["open"],
|
|
310
|
+
high: row["high"],
|
|
311
|
+
low: row["low"],
|
|
312
|
+
close: row["close"],
|
|
313
|
+
volume: row["volume"]
|
|
314
|
+
}
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def from_dhanhq(payload)
|
|
319
|
+
payload.fetch("close", []).each_index.map do |i|
|
|
320
|
+
{
|
|
321
|
+
timestamp: payload["timestamp"][i],
|
|
322
|
+
open: payload["open"][i],
|
|
323
|
+
high: payload["high"][i],
|
|
324
|
+
low: payload["low"][i],
|
|
325
|
+
close: payload["close"][i],
|
|
326
|
+
volume: payload["volume"][i]
|
|
327
|
+
}
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Warm-Up Values And `nil` Results
|
|
334
|
+
|
|
335
|
+
Most indicators need a minimum lookback window before they can return a value. Because of that, the first values are often `nil`.
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
IndicatorHub.sma([1, 2, 3, 4, 5], period: 3)
|
|
341
|
+
# => [nil, nil, 2.0, 3.0, 4.0]
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
In Rails or reporting code, it is common to use the latest non-`nil` value:
|
|
345
|
+
|
|
346
|
+
```ruby
|
|
347
|
+
latest_rsi = IndicatorHub.rsi(closes, period: 14).compact.last
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Common Patterns
|
|
351
|
+
|
|
352
|
+
Latest value only:
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
latest_ema = IndicatorHub.ema(closes, period: 20).compact.last
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Latest MACD snapshot:
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
latest_macd = IndicatorHub.macd(closes).last
|
|
362
|
+
# => { macd:, signal:, histogram: }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Multiple indicators from the same candle set:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
indicators = {
|
|
369
|
+
sma_20: IndicatorHub.sma(candles, period: 20, field: :close).last,
|
|
370
|
+
rsi_14: IndicatorHub.rsi(candles, period: 14, field: :close).last,
|
|
371
|
+
atr_14: IndicatorHub.atr(candles, period: 14).last,
|
|
372
|
+
obv: IndicatorHub.obv(candles).last
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Tips For Production Use
|
|
377
|
+
|
|
378
|
+
- Always sort your data chronologically before calculating indicators.
|
|
379
|
+
- Pass enough history for the indicator lookback plus warm-up.
|
|
380
|
+
- Use `field: :close` explicitly when your source objects contain multiple price fields.
|
|
381
|
+
- For API responses, prefer returning the latest computed point instead of the full series unless you need charting data.
|
|
382
|
+
- VWAP in this gem is cumulative over the provided dataset, so if you need session-based VWAP, pass one session at a time.
|
|
383
|
+
|
|
384
|
+
## API Reference By Indicator Category
|
|
385
|
+
|
|
386
|
+
### Single-Series Indicators
|
|
387
|
+
|
|
388
|
+
These work with:
|
|
389
|
+
|
|
390
|
+
- numeric arrays like `[100.0, 101.2, 102.4]`
|
|
391
|
+
- hash arrays with `field:` like `[{ close: 100.0 }, { close: 101.2 }]`
|
|
392
|
+
|
|
393
|
+
Methods:
|
|
394
|
+
|
|
395
|
+
- `IndicatorHub.sma(data, period: 20, field: :close)`
|
|
396
|
+
- `IndicatorHub.ema(data, period: 20, field: :close)`
|
|
397
|
+
- `IndicatorHub.wma(data, period: 20, field: :close)`
|
|
398
|
+
- `IndicatorHub.rsi(data, period: 14, field: :close)`
|
|
399
|
+
- `IndicatorHub.cmo(data, period: 14, field: :close)`
|
|
400
|
+
- `IndicatorHub.dlr(data, field: :close)`
|
|
401
|
+
- `IndicatorHub.dpo(data, period: 20, field: :close)`
|
|
402
|
+
- `IndicatorHub.dr(data, field: :close)`
|
|
403
|
+
- `IndicatorHub.roc(data, period: 12, field: :close)`
|
|
404
|
+
- `IndicatorHub.trix(data, period: 15, field: :close)`
|
|
405
|
+
- `IndicatorHub.tsi(data, fast_period: 13, slow_period: 25, field: :close)`
|
|
406
|
+
- `IndicatorHub.wilders_smoothing(data, period: 14, field: :close)`
|
|
407
|
+
- `IndicatorHub.cr(data, period: 20, field: :close)`
|
|
408
|
+
- `IndicatorHub.envelopes_ema(data, period: 20, percentage: 2.5, field: :close)`
|
|
409
|
+
- `IndicatorHub.macd(data, fast_period: 12, slow_period: 26, signal_period: 9, field: :close)`
|
|
410
|
+
- `IndicatorHub.rmi(data, period: 14, momentum_period: 5)`
|
|
411
|
+
|
|
412
|
+
### OHLCV Indicators
|
|
413
|
+
|
|
414
|
+
These expect candle hashes with `open`, `high`, `low`, `close`, and optionally `volume` where required:
|
|
415
|
+
|
|
416
|
+
```ruby
|
|
417
|
+
[
|
|
418
|
+
{ open: 100.0, high: 103.0, low: 99.0, close: 101.0, volume: 1200.0 }
|
|
419
|
+
]
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Methods:
|
|
423
|
+
|
|
424
|
+
- `IndicatorHub.adi(data)`
|
|
425
|
+
- `IndicatorHub.adtv(data, period: 20)`
|
|
426
|
+
- `IndicatorHub.adx(data, period: 14)`
|
|
427
|
+
- `IndicatorHub.ao(data, short_period: 5, long_period: 34)`
|
|
428
|
+
- `IndicatorHub.atr(data, period: 14)`
|
|
429
|
+
- `IndicatorHub.cci(data, period: 20)`
|
|
430
|
+
- `IndicatorHub.cmf(data, period: 20)`
|
|
431
|
+
- `IndicatorHub.dc(data, period: 20)`
|
|
432
|
+
- `IndicatorHub.eom(data, period: 14)`
|
|
433
|
+
- `IndicatorHub.fi(data, period: 13)`
|
|
434
|
+
- `IndicatorHub.ichimoku(data, low_period: 9, medium_period: 26, high_period: 52)`
|
|
435
|
+
- `IndicatorHub.imi(data, period: 14)`
|
|
436
|
+
- `IndicatorHub.kc(data, period: 20, multiplier: 1.5)`
|
|
437
|
+
- `IndicatorHub.mfi(data, period: 14)`
|
|
438
|
+
- `IndicatorHub.mi(data, period: 25)`
|
|
439
|
+
- `IndicatorHub.nvi(data)`
|
|
440
|
+
- `IndicatorHub.obv(data)`
|
|
441
|
+
- `IndicatorHub.obv_mean(data, period: 10)`
|
|
442
|
+
- `IndicatorHub.pivot_points(data)`
|
|
443
|
+
- `IndicatorHub.price_channel(data, period: 20)`
|
|
444
|
+
- `IndicatorHub.qstick(data, period: 10)`
|
|
445
|
+
- `IndicatorHub.so(data, k_period: 14, k_slowing: 3, d_period: 3)`
|
|
446
|
+
- `IndicatorHub.uo(data, short_period: 7, medium_period: 14, long_period: 28)`
|
|
447
|
+
- `IndicatorHub.vi(data, period: 14)`
|
|
448
|
+
- `IndicatorHub.volume_oscillator(data, short_period: 20, long_period: 60)`
|
|
449
|
+
- `IndicatorHub.vpt(data)`
|
|
450
|
+
- `IndicatorHub.vwap(data)`
|
|
451
|
+
- `IndicatorHub.wr(data, period: 14)`
|
|
452
|
+
|
|
453
|
+
### Indicators Returning Hashes
|
|
454
|
+
|
|
455
|
+
These return structured values instead of a plain numeric series:
|
|
456
|
+
|
|
457
|
+
- `IndicatorHub.bb` returns `{ upper:, middle:, lower: }`
|
|
458
|
+
- `IndicatorHub.macd` returns `{ macd:, signal:, histogram: }`
|
|
459
|
+
- `IndicatorHub.dc` returns `{ upper:, middle:, lower: }`
|
|
460
|
+
- `IndicatorHub.envelopes_ema` returns `{ upper:, middle:, lower: }`
|
|
461
|
+
- `IndicatorHub.ichimoku` returns `{ tenkan_sen:, kijun_sen:, senkou_span_a:, senkou_span_b:, chikou_span: }`
|
|
462
|
+
- `IndicatorHub.kc` returns `{ upper:, middle:, lower: }`
|
|
463
|
+
- `IndicatorHub.kst` returns `{ kst:, signal: }`
|
|
464
|
+
- `IndicatorHub.pivot_points` returns `{ p:, s1:, s2:, s3:, r1:, r2:, r3: }`
|
|
465
|
+
- `IndicatorHub.price_channel` returns `{ upper:, lower: }`
|
|
466
|
+
- `IndicatorHub.so` returns `{ k:, d: }`
|
|
467
|
+
- `IndicatorHub.vi` returns `{ plus_vi:, minus_vi: }`
|
|
468
|
+
|
|
469
|
+
## Basic Examples
|
|
470
|
+
|
|
471
|
+
```ruby
|
|
472
|
+
require 'indicator_hub'
|
|
473
|
+
|
|
474
|
+
data = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0]
|
|
475
|
+
|
|
476
|
+
# Simple Moving Average
|
|
477
|
+
sma = IndicatorHub.sma(data, period: 5)
|
|
478
|
+
# => [nil, nil, nil, nil, 12.0, 13.0]
|
|
479
|
+
|
|
480
|
+
# Relative Strength Index
|
|
481
|
+
rsi = IndicatorHub.rsi(data, period: 5)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
OHLCV data:
|
|
485
|
+
|
|
486
|
+
```ruby
|
|
487
|
+
data = [
|
|
488
|
+
{ timestamp: 1704067200, open: 10, high: 12, low: 9, close: 11, volume: 1000 },
|
|
489
|
+
{ timestamp: 1704153600, open: 11, high: 13, low: 10, close: 12, volume: 1200 },
|
|
490
|
+
# ...
|
|
491
|
+
]
|
|
492
|
+
|
|
493
|
+
# Calculate SMA on the 'close' field
|
|
494
|
+
sma = IndicatorHub.sma(data, period: 2, field: :close)
|
|
495
|
+
|
|
496
|
+
# Calculate ATR from OHLCV candles
|
|
497
|
+
atr = IndicatorHub.atr(data, period: 14)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Supported Indicators
|
|
501
|
+
|
|
502
|
+
- `IndicatorHub.sma(data, period: 20)`
|
|
503
|
+
- `IndicatorHub.ema(data, period: 20)`
|
|
504
|
+
- `IndicatorHub.rsi(data, period: 14)`
|
|
505
|
+
- `IndicatorHub.macd(data, fast_period: 12, slow_period: 26, signal_period: 9)`
|
|
506
|
+
- `IndicatorHub.bb(data, period: 20, standard_deviations: 2)`
|
|
507
|
+
|
|
508
|
+
## Contributing
|
|
509
|
+
|
|
510
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/shubhamtaywade/indicator_hub.
|
|
511
|
+
|
|
512
|
+
## Release Process
|
|
513
|
+
|
|
514
|
+
CI:
|
|
515
|
+
|
|
516
|
+
- GitHub Actions runs on `main`, `master`, and pull requests
|
|
517
|
+
- The CI workflow tests Ruby `3.2.0` and `3.3.4`
|
|
518
|
+
- Each CI run executes `bundle exec rake` and verifies the gem builds successfully
|
|
519
|
+
|
|
520
|
+
CD:
|
|
521
|
+
|
|
522
|
+
- Releases are triggered by pushing a tag like `v0.1.0`
|
|
523
|
+
- The release workflow validates that the tag matches `IndicatorHub::VERSION`
|
|
524
|
+
- It runs the full test/lint suite, builds the gem, and publishes to RubyGems
|
|
525
|
+
|
|
526
|
+
Required GitHub Actions secrets:
|
|
527
|
+
|
|
528
|
+
- `RUBYGEMS_API_KEY`
|
|
529
|
+
- `RUBYGEMS_OTP_SECRET`
|
|
530
|
+
|
|
531
|
+
Typical release steps:
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
# 1. Update version
|
|
535
|
+
# lib/indicator_hub/version.rb
|
|
536
|
+
|
|
537
|
+
# 2. Update changelog
|
|
538
|
+
# CHANGELOG.md
|
|
539
|
+
|
|
540
|
+
# 3. Commit changes
|
|
541
|
+
git add .
|
|
542
|
+
git commit -m "Release v0.1.0"
|
|
543
|
+
|
|
544
|
+
# 4. Create and push tag
|
|
545
|
+
git tag v0.1.0
|
|
546
|
+
git push origin main --tags
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## License
|
|
550
|
+
|
|
551
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/exe/indicator_hub
ADDED