sqa-tai 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3df8b9f48bc99341d38af4690c0a82a3a6f648890bfeaf8d45e92aec5d591e25
4
- data.tar.gz: 60e3356d7f7911700dd05efb166817caf9a42ceb718b261088bc3659cc3e5b87
3
+ metadata.gz: 0c708d46e3d535588af3a14a4d209e32e79a34fa11733c3a20cec8e8a25ddf8f
4
+ data.tar.gz: 665274f5b878d3165f50edfc78a6c6903c95f3696483357d07b12b929f2a00fb
5
5
  SHA512:
6
- metadata.gz: 13e78aa5c9e871041502c71860f827896de687363a06797fb43d2a9522fb9664e1ecd8efd0fd2aecbedad6c23a58af4f6e648182b7509deea90377ff497b7ace
7
- data.tar.gz: 4045b21ddbbe25fe6d73aa52c3b9a695ce6f5fcbdc520057aef403f3474091b4f6feb1d200e0bdd510d8cfed6c2aeede44591de5888a8072ce223119dec2f517
6
+ metadata.gz: 72e84001d6624688c24800916e2d9651a13d4047e3dc10e6dd53725b1ea464748890df15e4c35b74e1def91429dc2819750d87529cdb86f2d4fa3a8295fca706
7
+ data.tar.gz: b0a2341c3d7777326cddd41f50254f9ff5a290c73b63a6782495fc5c94a29759daa7d0d752cc82e1ea2cd3f1f749bb866ed69f76ba89d12cb1afc13959851a66
data/CHANGELOG.md CHANGED
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.1] 2025-11-13
11
+ ### Added
12
+ - Intraday Momentum Index (IMI) indicator
13
+ - GitHub Actions workflow to automatically deploy documentation to GitHub Pages
14
+ - Indicator template markdown file for documentation
15
+ - Chronological ordering note to indicator documentation
16
+
17
+ ### Fixed
18
+ - Hash return format handling from newer ta_lib_ffi versions
19
+ - Image alignment and naming in README
20
+ - API reference documentation formatting and index links
21
+
22
+ ### Changed
23
+ - Refactored library code into modular structure with 8 focused modules organized by indicator category
24
+ - `lib/sqa/tai/overlap_studies.rb` - Moving averages and bands
25
+ - `lib/sqa/tai/momentum_indicators.rb` - Momentum and oscillator indicators
26
+ - `lib/sqa/tai/volatility_indicators.rb` - Volatility and range indicators
27
+ - `lib/sqa/tai/volume_indicators.rb` - Volume-based indicators
28
+ - `lib/sqa/tai/price_transform.rb` - Price transformation functions
29
+ - `lib/sqa/tai/cycle_indicators.rb` - Hilbert Transform cycle indicators
30
+ - `lib/sqa/tai/statistical_functions.rb` - Statistical analysis functions
31
+ - `lib/sqa/tai/pattern_recognition.rb` - Candlestick pattern recognition
32
+ - Refactored test suite to mirror lib directory organization with 8 modular test files
33
+ - Reduced main `lib/sqa/tai.rb` from 1,851 lines to 67 lines (96% reduction)
34
+ - Reduced main `test/sqa/tai_test.rb` to core tests only, extracted 72 tests into category-specific files
35
+ - Updated indicator count to 132
36
+ - Reorganized README sections
37
+ - Improved SQA name from "Stock Qualitative Analysis" to "Simple Qualitative Analysis"
38
+ - Updated gemspec description
39
+
10
40
  ## [0.1.0] - 2025-11-06
11
41
 
12
42
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sqa-tai (0.1.0)
4
+ sqa-tai (0.1.1)
5
5
  ta_lib_ffi (~> 0.3)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  <table>
9
9
  <tr>
10
- <td width="30%" valign="top" align="center">
11
- <img src="docs/assets/images/sqa-tai.jpg" alt="Ruby Turns Information into Knowledge" width="60%">
10
+ <td width="30%" valign="middle" align="center">
11
+ <img src="docs/assets/images/sqa.jpg" alt="Ruby Turns Information into Knowledge" width="80%">
12
12
  <br/>
13
13
  </td>
14
14
  <td width="70%" valign="top">
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SQA
4
+ module TAI
5
+ # Cycle Indicators
6
+ module CycleIndicators
7
+ # Hilbert Transform - Dominant Cycle Period
8
+ # @param prices [Array<Float>] Array of prices
9
+ # @return [Array<Float>] Dominant cycle period values
10
+ def ht_dcperiod(prices)
11
+ check_available!
12
+ validate_prices!(prices)
13
+
14
+ TALibFFI.ht_dcperiod(prices)
15
+ end
16
+
17
+ # Hilbert Transform - Trend vs Cycle Mode
18
+ # @param prices [Array<Float>] Array of prices
19
+ # @return [Array<Integer>] Trend mode (1) or cycle mode (0)
20
+ def ht_trendmode(prices)
21
+ check_available!
22
+ validate_prices!(prices)
23
+
24
+ TALibFFI.ht_trendmode(prices)
25
+ end
26
+
27
+ # Hilbert Transform - Dominant Cycle Phase
28
+ # @param prices [Array<Float>] Array of prices
29
+ # @return [Array<Float>] Dominant cycle phase values
30
+ def ht_dcphase(prices)
31
+ check_available!
32
+ validate_prices!(prices)
33
+
34
+ TALibFFI.ht_dcphase(prices)
35
+ end
36
+
37
+ # Hilbert Transform - Phasor Components
38
+ # @param prices [Array<Float>] Array of prices
39
+ # @return [Array<Array<Float>>] [inphase, quadrature]
40
+ def ht_phasor(prices)
41
+ check_available!
42
+ validate_prices!(prices)
43
+
44
+ result = TALibFFI.ht_phasor(prices)
45
+
46
+ # Handle hash return format from newer ta_lib_ffi versions
47
+ if result.is_a?(Hash)
48
+ [result[:in_phase], result[:quadrature]]
49
+ else
50
+ result
51
+ end
52
+ end
53
+
54
+ # Hilbert Transform - SineWave
55
+ # @param prices [Array<Float>] Array of prices
56
+ # @return [Array<Array<Float>>] [sine, lead_sine]
57
+ def ht_sine(prices)
58
+ check_available!
59
+ validate_prices!(prices)
60
+
61
+ result = TALibFFI.ht_sine(prices)
62
+
63
+ # Handle hash return format from newer ta_lib_ffi versions
64
+ if result.is_a?(Hash)
65
+ [result[:sine], result[:lead_sine]]
66
+ else
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,517 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SQA
4
+ module TAI
5
+ # Momentum Indicators
6
+ module MomentumIndicators
7
+ # Relative Strength Index
8
+ # @param prices [Array<Float>] Array of prices
9
+ # @param period [Integer] Time period (default: 14)
10
+ # @return [Array<Float>] RSI values
11
+ def rsi(prices, period: 14)
12
+ check_available!
13
+ validate_prices!(prices)
14
+ validate_period!(period, prices.size)
15
+
16
+ TALibFFI.rsi(prices, time_period: period)
17
+ end
18
+
19
+ # Intraday Momentum Index
20
+ # @param open_prices [Array<Float>] Array of open prices
21
+ # @param close_prices [Array<Float>] Array of close prices
22
+ # @param period [Integer] Time period (default: 14)
23
+ # @return [Array<Float>] IMI values
24
+ def imi(open_prices, close_prices, period: 14)
25
+ check_available!
26
+ validate_prices!(open_prices)
27
+ validate_prices!(close_prices)
28
+ validate_period!(period, [open_prices.size, close_prices.size].min)
29
+
30
+ TALibFFI.imi(open_prices, close_prices, time_period: period)
31
+ end
32
+
33
+ # Moving Average Convergence/Divergence
34
+ # @param prices [Array<Float>] Array of prices
35
+ # @param fast_period [Integer] Fast period (default: 12)
36
+ # @param slow_period [Integer] Slow period (default: 26)
37
+ # @param signal_period [Integer] Signal period (default: 9)
38
+ # @return [Array<Array<Float>>] [macd, signal, histogram]
39
+ def macd(prices, fast_period: 12, slow_period: 26, signal_period: 9)
40
+ check_available!
41
+ validate_prices!(prices)
42
+
43
+ result = TALibFFI.macd(
44
+ prices,
45
+ fast_period: fast_period,
46
+ slow_period: slow_period,
47
+ signal_period: signal_period
48
+ )
49
+
50
+ # Handle hash return format from newer ta_lib_ffi versions
51
+ if result.is_a?(Hash)
52
+ [result[:macd], result[:macd_signal], result[:macd_hist]]
53
+ else
54
+ result
55
+ end
56
+ end
57
+
58
+ # Stochastic Oscillator
59
+ # @param high [Array<Float>] High prices
60
+ # @param low [Array<Float>] Low prices
61
+ # @param close [Array<Float>] Close prices
62
+ # @param fastk_period [Integer] Fast K period (default: 5)
63
+ # @param slowk_period [Integer] Slow K period (default: 3)
64
+ # @param slowd_period [Integer] Slow D period (default: 3)
65
+ # @return [Array<Array<Float>>] [slowk, slowd]
66
+ def stoch(high, low, close, fastk_period: 5, slowk_period: 3, slowd_period: 3)
67
+ check_available!
68
+ validate_prices!(high)
69
+ validate_prices!(low)
70
+ validate_prices!(close)
71
+
72
+ result = TALibFFI.stoch(
73
+ high,
74
+ low,
75
+ close,
76
+ fastk_period: fastk_period,
77
+ slowk_period: slowk_period,
78
+ slowd_period: slowd_period
79
+ )
80
+
81
+ # Handle hash return format from newer ta_lib_ffi versions
82
+ if result.is_a?(Hash)
83
+ [result[:slow_k], result[:slow_d]]
84
+ else
85
+ result
86
+ end
87
+ end
88
+
89
+ # Momentum
90
+ # @param prices [Array<Float>] Array of prices
91
+ # @param period [Integer] Time period (default: 10)
92
+ # @return [Array<Float>] Momentum values
93
+ def mom(prices, period: 10)
94
+ check_available!
95
+ validate_prices!(prices)
96
+ validate_period!(period, prices.size)
97
+
98
+ TALibFFI.mom(prices, time_period: period)
99
+ end
100
+
101
+ # Commodity Channel Index
102
+ # @param high [Array<Float>] High prices
103
+ # @param low [Array<Float>] Low prices
104
+ # @param close [Array<Float>] Close prices
105
+ # @param period [Integer] Time period (default: 14)
106
+ # @return [Array<Float>] CCI values
107
+ def cci(high, low, close, period: 14)
108
+ check_available!
109
+ validate_prices!(high)
110
+ validate_prices!(low)
111
+ validate_prices!(close)
112
+
113
+ TALibFFI.cci(high, low, close, time_period: period)
114
+ end
115
+
116
+ # Williams' %R
117
+ # @param high [Array<Float>] High prices
118
+ # @param low [Array<Float>] Low prices
119
+ # @param close [Array<Float>] Close prices
120
+ # @param period [Integer] Time period (default: 14)
121
+ # @return [Array<Float>] WILLR values
122
+ def willr(high, low, close, period: 14)
123
+ check_available!
124
+ validate_prices!(high)
125
+ validate_prices!(low)
126
+ validate_prices!(close)
127
+
128
+ TALibFFI.willr(high, low, close, time_period: period)
129
+ end
130
+
131
+ # Rate of Change
132
+ # @param prices [Array<Float>] Array of prices
133
+ # @param period [Integer] Time period (default: 10)
134
+ # @return [Array<Float>] ROC values
135
+ def roc(prices, period: 10)
136
+ check_available!
137
+ validate_prices!(prices)
138
+ validate_period!(period, prices.size)
139
+
140
+ TALibFFI.roc(prices, time_period: period)
141
+ end
142
+
143
+ # Rate of Change Percentage
144
+ # @param prices [Array<Float>] Array of prices
145
+ # @param period [Integer] Time period (default: 10)
146
+ # @return [Array<Float>] ROCP values
147
+ def rocp(prices, period: 10)
148
+ check_available!
149
+ validate_prices!(prices)
150
+ validate_period!(period, prices.size)
151
+
152
+ TALibFFI.rocp(prices, time_period: period)
153
+ end
154
+
155
+ # Rate of Change Ratio
156
+ # @param prices [Array<Float>] Array of prices
157
+ # @param period [Integer] Time period (default: 10)
158
+ # @return [Array<Float>] ROCR values
159
+ def rocr(prices, period: 10)
160
+ check_available!
161
+ validate_prices!(prices)
162
+ validate_period!(period, prices.size)
163
+
164
+ TALibFFI.rocr(prices, time_period: period)
165
+ end
166
+
167
+ # Percentage Price Oscillator
168
+ # @param prices [Array<Float>] Array of prices
169
+ # @param fast_period [Integer] Fast period (default: 12)
170
+ # @param slow_period [Integer] Slow period (default: 26)
171
+ # @param ma_type [Integer] Moving average type (default: 0)
172
+ # @return [Array<Float>] PPO values
173
+ def ppo(prices, fast_period: 12, slow_period: 26, ma_type: 0)
174
+ check_available!
175
+ validate_prices!(prices)
176
+
177
+ TALibFFI.ppo(prices, fast_period: fast_period, slow_period: slow_period, ma_type: ma_type)
178
+ end
179
+
180
+ # Average Directional Movement Index
181
+ # @param high [Array<Float>] High prices
182
+ # @param low [Array<Float>] Low prices
183
+ # @param close [Array<Float>] Close prices
184
+ # @param period [Integer] Time period (default: 14)
185
+ # @return [Array<Float>] ADX values
186
+ def adx(high, low, close, period: 14)
187
+ check_available!
188
+ validate_prices!(high)
189
+ validate_prices!(low)
190
+ validate_prices!(close)
191
+
192
+ TALibFFI.adx(high, low, close, time_period: period)
193
+ end
194
+
195
+ # Average Directional Movement Index Rating
196
+ # @param high [Array<Float>] High prices
197
+ # @param low [Array<Float>] Low prices
198
+ # @param close [Array<Float>] Close prices
199
+ # @param period [Integer] Time period (default: 14)
200
+ # @return [Array<Float>] ADXR values
201
+ def adxr(high, low, close, period: 14)
202
+ check_available!
203
+ validate_prices!(high)
204
+ validate_prices!(low)
205
+ validate_prices!(close)
206
+
207
+ TALibFFI.adxr(high, low, close, time_period: period)
208
+ end
209
+
210
+ # Absolute Price Oscillator
211
+ # @param prices [Array<Float>] Array of prices
212
+ # @param fast_period [Integer] Fast period (default: 12)
213
+ # @param slow_period [Integer] Slow period (default: 26)
214
+ # @param ma_type [Integer] Moving average type (default: 0)
215
+ # @return [Array<Float>] APO values
216
+ def apo(prices, fast_period: 12, slow_period: 26, ma_type: 0)
217
+ check_available!
218
+ validate_prices!(prices)
219
+
220
+ TALibFFI.apo(prices, fast_period: fast_period, slow_period: slow_period, ma_type: ma_type)
221
+ end
222
+
223
+ # Aroon
224
+ # @param high [Array<Float>] High prices
225
+ # @param low [Array<Float>] Low prices
226
+ # @param period [Integer] Time period (default: 14)
227
+ # @return [Array<Array<Float>>] [aroon_down, aroon_up]
228
+ def aroon(high, low, period: 14)
229
+ check_available!
230
+ validate_prices!(high)
231
+ validate_prices!(low)
232
+
233
+ result = TALibFFI.aroon(high, low, time_period: period)
234
+
235
+ # Handle hash return format from newer ta_lib_ffi versions
236
+ if result.is_a?(Hash)
237
+ [result[:aroon_down], result[:aroon_up]]
238
+ else
239
+ result
240
+ end
241
+ end
242
+
243
+ # Aroon Oscillator
244
+ # @param high [Array<Float>] High prices
245
+ # @param low [Array<Float>] Low prices
246
+ # @param period [Integer] Time period (default: 14)
247
+ # @return [Array<Float>] AROONOSC values
248
+ def aroonosc(high, low, period: 14)
249
+ check_available!
250
+ validate_prices!(high)
251
+ validate_prices!(low)
252
+
253
+ TALibFFI.aroonosc(high, low, time_period: period)
254
+ end
255
+
256
+ # Balance of Power
257
+ # @param open [Array<Float>] Open prices
258
+ # @param high [Array<Float>] High prices
259
+ # @param low [Array<Float>] Low prices
260
+ # @param close [Array<Float>] Close prices
261
+ # @return [Array<Float>] BOP values
262
+ def bop(open, high, low, close)
263
+ check_available!
264
+ validate_prices!(open)
265
+ validate_prices!(high)
266
+ validate_prices!(low)
267
+ validate_prices!(close)
268
+
269
+ TALibFFI.bop(open, high, low, close)
270
+ end
271
+
272
+ # Chande Momentum Oscillator
273
+ # @param prices [Array<Float>] Array of prices
274
+ # @param period [Integer] Time period (default: 14)
275
+ # @return [Array<Float>] CMO values
276
+ def cmo(prices, period: 14)
277
+ check_available!
278
+ validate_prices!(prices)
279
+ validate_period!(period, prices.size)
280
+
281
+ TALibFFI.cmo(prices, time_period: period)
282
+ end
283
+
284
+ # Directional Movement Index
285
+ # @param high [Array<Float>] High prices
286
+ # @param low [Array<Float>] Low prices
287
+ # @param close [Array<Float>] Close prices
288
+ # @param period [Integer] Time period (default: 14)
289
+ # @return [Array<Float>] DX values
290
+ def dx(high, low, close, period: 14)
291
+ check_available!
292
+ validate_prices!(high)
293
+ validate_prices!(low)
294
+ validate_prices!(close)
295
+
296
+ TALibFFI.dx(high, low, close, time_period: period)
297
+ end
298
+
299
+ # MACD with Controllable MA Type
300
+ # @param prices [Array<Float>] Array of prices
301
+ # @param fast_period [Integer] Fast period (default: 12)
302
+ # @param fast_ma_type [Integer] Fast MA type (default: 0)
303
+ # @param slow_period [Integer] Slow period (default: 26)
304
+ # @param slow_ma_type [Integer] Slow MA type (default: 0)
305
+ # @param signal_period [Integer] Signal period (default: 9)
306
+ # @param signal_ma_type [Integer] Signal MA type (default: 0)
307
+ # @return [Array<Array<Float>>] [macd, signal, histogram]
308
+ def macdext(prices, fast_period: 12, fast_ma_type: 0, slow_period: 26, slow_ma_type: 0, signal_period: 9, signal_ma_type: 0)
309
+ check_available!
310
+ validate_prices!(prices)
311
+
312
+ result = TALibFFI.macdext(
313
+ prices,
314
+ fast_period: fast_period,
315
+ fast_ma_type: fast_ma_type,
316
+ slow_period: slow_period,
317
+ slow_ma_type: slow_ma_type,
318
+ signal_period: signal_period,
319
+ signal_ma_type: signal_ma_type
320
+ )
321
+
322
+ # Handle hash return format from newer ta_lib_ffi versions
323
+ if result.is_a?(Hash)
324
+ [result[:macd], result[:macd_signal], result[:macd_hist]]
325
+ else
326
+ result
327
+ end
328
+ end
329
+
330
+ # MACD Fix 12/26
331
+ # @param prices [Array<Float>] Array of prices
332
+ # @param signal_period [Integer] Signal period (default: 9)
333
+ # @return [Array<Array<Float>>] [macd, signal, histogram]
334
+ def macdfix(prices, signal_period: 9)
335
+ check_available!
336
+ validate_prices!(prices)
337
+
338
+ result = TALibFFI.macdfix(prices, signal_period: signal_period)
339
+
340
+ # Handle hash return format from newer ta_lib_ffi versions
341
+ if result.is_a?(Hash)
342
+ [result[:macd], result[:macd_signal], result[:macd_hist]]
343
+ else
344
+ result
345
+ end
346
+ end
347
+
348
+ # Money Flow Index
349
+ # @param high [Array<Float>] High prices
350
+ # @param low [Array<Float>] Low prices
351
+ # @param close [Array<Float>] Close prices
352
+ # @param volume [Array<Float>] Volume values
353
+ # @param period [Integer] Time period (default: 14)
354
+ # @return [Array<Float>] MFI values
355
+ def mfi(high, low, close, volume, period: 14)
356
+ check_available!
357
+ validate_prices!(high)
358
+ validate_prices!(low)
359
+ validate_prices!(close)
360
+ validate_prices!(volume)
361
+
362
+ TALibFFI.mfi(high, low, close, volume, time_period: period)
363
+ end
364
+
365
+ # Minus Directional Indicator
366
+ # @param high [Array<Float>] High prices
367
+ # @param low [Array<Float>] Low prices
368
+ # @param close [Array<Float>] Close prices
369
+ # @param period [Integer] Time period (default: 14)
370
+ # @return [Array<Float>] MINUS_DI values
371
+ def minus_di(high, low, close, period: 14)
372
+ check_available!
373
+ validate_prices!(high)
374
+ validate_prices!(low)
375
+ validate_prices!(close)
376
+
377
+ TALibFFI.minus_di(high, low, close, time_period: period)
378
+ end
379
+
380
+ # Minus Directional Movement
381
+ # @param high [Array<Float>] High prices
382
+ # @param low [Array<Float>] Low prices
383
+ # @param period [Integer] Time period (default: 14)
384
+ # @return [Array<Float>] MINUS_DM values
385
+ def minus_dm(high, low, period: 14)
386
+ check_available!
387
+ validate_prices!(high)
388
+ validate_prices!(low)
389
+
390
+ TALibFFI.minus_dm(high, low, time_period: period)
391
+ end
392
+
393
+ # Plus Directional Indicator
394
+ # @param high [Array<Float>] High prices
395
+ # @param low [Array<Float>] Low prices
396
+ # @param close [Array<Float>] Close prices
397
+ # @param period [Integer] Time period (default: 14)
398
+ # @return [Array<Float>] PLUS_DI values
399
+ def plus_di(high, low, close, period: 14)
400
+ check_available!
401
+ validate_prices!(high)
402
+ validate_prices!(low)
403
+ validate_prices!(close)
404
+
405
+ TALibFFI.plus_di(high, low, close, time_period: period)
406
+ end
407
+
408
+ # Plus Directional Movement
409
+ # @param high [Array<Float>] High prices
410
+ # @param low [Array<Float>] Low prices
411
+ # @param period [Integer] Time period (default: 14)
412
+ # @return [Array<Float>] PLUS_DM values
413
+ def plus_dm(high, low, period: 14)
414
+ check_available!
415
+ validate_prices!(high)
416
+ validate_prices!(low)
417
+
418
+ TALibFFI.plus_dm(high, low, time_period: period)
419
+ end
420
+
421
+ # Rate of Change Ratio 100 scale
422
+ # @param prices [Array<Float>] Array of prices
423
+ # @param period [Integer] Time period (default: 10)
424
+ # @return [Array<Float>] ROCR100 values
425
+ def rocr100(prices, period: 10)
426
+ check_available!
427
+ validate_prices!(prices)
428
+ validate_period!(period, prices.size)
429
+
430
+ TALibFFI.rocr100(prices, time_period: period)
431
+ end
432
+
433
+ # Stochastic Fast
434
+ # @param high [Array<Float>] High prices
435
+ # @param low [Array<Float>] Low prices
436
+ # @param close [Array<Float>] Close prices
437
+ # @param fastk_period [Integer] Fast K period (default: 5)
438
+ # @param fastd_period [Integer] Fast D period (default: 3)
439
+ # @return [Array<Array<Float>>] [fastk, fastd]
440
+ def stochf(high, low, close, fastk_period: 5, fastd_period: 3)
441
+ check_available!
442
+ validate_prices!(high)
443
+ validate_prices!(low)
444
+ validate_prices!(close)
445
+
446
+ result = TALibFFI.stochf(
447
+ high,
448
+ low,
449
+ close,
450
+ fastk_period: fastk_period,
451
+ fastd_period: fastd_period
452
+ )
453
+
454
+ # Handle hash return format from newer ta_lib_ffi versions
455
+ if result.is_a?(Hash)
456
+ [result[:fast_k], result[:fast_d]]
457
+ else
458
+ result
459
+ end
460
+ end
461
+
462
+ # Stochastic RSI
463
+ # @param prices [Array<Float>] Array of prices
464
+ # @param period [Integer] Time period (default: 14)
465
+ # @param fastk_period [Integer] Fast K period (default: 5)
466
+ # @param fastd_period [Integer] Fast D period (default: 3)
467
+ # @return [Array<Array<Float>>] [fastk, fastd]
468
+ def stochrsi(prices, period: 14, fastk_period: 5, fastd_period: 3)
469
+ check_available!
470
+ validate_prices!(prices)
471
+
472
+ result = TALibFFI.stochrsi(
473
+ prices,
474
+ time_period: period,
475
+ fastk_period: fastk_period,
476
+ fastd_period: fastd_period
477
+ )
478
+
479
+ # Handle hash return format from newer ta_lib_ffi versions
480
+ if result.is_a?(Hash)
481
+ [result[:fast_k], result[:fast_d]]
482
+ else
483
+ result
484
+ end
485
+ end
486
+
487
+ # 1-day Rate-Of-Change (ROC) of a Triple Smooth EMA
488
+ # @param prices [Array<Float>] Array of prices
489
+ # @param period [Integer] Time period (default: 30)
490
+ # @return [Array<Float>] TRIX values
491
+ def trix(prices, period: 30)
492
+ check_available!
493
+ validate_prices!(prices)
494
+ validate_period!(period, prices.size)
495
+
496
+ TALibFFI.trix(prices, time_period: period)
497
+ end
498
+
499
+ # Ultimate Oscillator
500
+ # @param high [Array<Float>] High prices
501
+ # @param low [Array<Float>] Low prices
502
+ # @param close [Array<Float>] Close prices
503
+ # @param period1 [Integer] First period (default: 7)
504
+ # @param period2 [Integer] Second period (default: 14)
505
+ # @param period3 [Integer] Third period (default: 28)
506
+ # @return [Array<Float>] ULTOSC values
507
+ def ultosc(high, low, close, period1: 7, period2: 14, period3: 28)
508
+ check_available!
509
+ validate_prices!(high)
510
+ validate_prices!(low)
511
+ validate_prices!(close)
512
+
513
+ TALibFFI.ultosc(high, low, close, time_period1: period1, time_period2: period2, time_period3: period3)
514
+ end
515
+ end
516
+ end
517
+ end