sqa-tai 0.1.2 → 0.4.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.
@@ -0,0 +1,497 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiddle"
4
+ require "fiddle/import"
5
+
6
+ module SQA
7
+ module TAI
8
+ # Direct Fiddle binding against the installed TA-Lib C library's
9
+ # abstract interface. Replaces the (now dropped) `ta_lib_ffi` gem,
10
+ # which was unmaintained and carried a struct-layout bug that made
11
+ # it incompatible with current TA-Lib builds (see TA_FuncInfo below).
12
+ #
13
+ # Uses TA-Lib's own introspection API (TA_GetFuncInfo /
14
+ # TA_Get{Input,OptInput,Output}ParameterInfo) to discover each
15
+ # function's signature at runtime instead of hand-writing ~158
16
+ # bindings, then dispatches generically through method_missing.
17
+ #
18
+ # rubocop:disable Metrics/ModuleLength -- one cohesive FFI binding:
19
+ # struct/const declarations plus the introspection + marshaling logic
20
+ # that reads them. Splitting it would separate code that must stay in
21
+ # lockstep with the installed TA-Lib header.
22
+ #
23
+ # :reek:TooManyConstants -- structs, type-tag maps, and value-object
24
+ # classes for TA-Lib's abstract interface; inherent to binding a C
25
+ # library's struct/enum surface, already trimmed to the minimum (see
26
+ # the comment above TA_SUCCESS/TA_FUNC_NOT_FOUND for how the 17
27
+ # TA_RetCode constants were folded away).
28
+ module Native
29
+ extend Fiddle::Importer
30
+
31
+ lib_path =
32
+ case RUBY_PLATFORM
33
+ when /darwin/
34
+ brew_prefix = `brew --prefix`.chomp
35
+ "#{brew_prefix}/lib/libta-lib.dylib"
36
+ when /linux/
37
+ "libta-lib.so"
38
+ when /cygwin|mswin|mingw|bccwin|wince|emx/
39
+ "C:/Program Files/TA-Lib/bin/ta-lib.dll"
40
+ else
41
+ raise "Unsupported platform: #{RUBY_PLATFORM}"
42
+ end
43
+
44
+ dlload lib_path
45
+
46
+ class TALibError < StandardError; end
47
+
48
+ # Only TA_SUCCESS and TA_FUNC_NOT_FOUND are compared against directly
49
+ # elsewhere in this file; every other TA_RetCode is only ever used to
50
+ # look up its message below, so those live solely as TA_ERROR_MESSAGES
51
+ # keys instead of 17 more standalone constants.
52
+ TA_SUCCESS = 0
53
+ TA_FUNC_NOT_FOUND = 5
54
+
55
+ typealias "TA_Real", "double"
56
+ typealias "TA_Integer", "int"
57
+ typealias "TA_FuncFlags", "int"
58
+ typealias "TA_InputParameterType", "int"
59
+ typealias "TA_InputFlags", "int"
60
+ typealias "TA_OptInputParameterType", "int"
61
+ typealias "TA_OptInputFlags", "int"
62
+ typealias "TA_OutputParameterType", "int"
63
+ typealias "TA_OutputFlags", "int"
64
+
65
+ # Matches $(brew --prefix)/include/ta-lib/ta_abstract.h's TA_FuncInfo.
66
+ #
67
+ # The installed TA-Lib C library (Homebrew's 0.8.x community fork)
68
+ # does NOT have a `camelCaseName` field here, even though older
69
+ # TA-Lib builds (and the abandoned ta_lib_ffi gem this replaces)
70
+ # assumed one. Keeping that phantom field misaligns every field
71
+ # after `hint` (flags/nbInput/nbOptInput/nbOutput/handle), so
72
+ # introspection reads garbage counts and spins forever allocating
73
+ # memory. Verify against the installed header before ever changing
74
+ # this struct.
75
+ TA_FuncInfo = struct [
76
+ "const char *name",
77
+ "const char *group",
78
+ "const char *hint",
79
+ "TA_FuncFlags flags",
80
+ "unsigned int nbInput",
81
+ "unsigned int nbOptInput",
82
+ "unsigned int nbOutput",
83
+ "const void *handle"
84
+ ]
85
+
86
+ TA_InputParameterInfo = struct [
87
+ "TA_InputParameterType type",
88
+ "const char *paramName",
89
+ "TA_InputFlags flags"
90
+ ]
91
+
92
+ TA_OptInputParameterInfo = struct [
93
+ "TA_OptInputParameterType type",
94
+ "const char *paramName",
95
+ "TA_OptInputFlags flags",
96
+ "const char *displayName",
97
+ "const void *dataSet",
98
+ "TA_Real defaultValue",
99
+ "const char *hint",
100
+ "const char *helpFile"
101
+ ]
102
+
103
+ TA_OutputParameterInfo = struct [
104
+ "TA_OutputParameterType type",
105
+ "const char *paramName",
106
+ "TA_OutputFlags flags"
107
+ ]
108
+
109
+ TA_PARAM_TYPE = {
110
+ TA_Input_Price: 0,
111
+ TA_Input_Real: 1,
112
+ TA_Input_Integer: 2,
113
+ TA_OptInput_RealRange: 0,
114
+ TA_OptInput_RealList: 1,
115
+ TA_OptInput_IntegerRange: 2,
116
+ TA_OptInput_IntegerList: 3,
117
+ TA_Output_Real: 0,
118
+ TA_Output_Integer: 1
119
+ }.freeze
120
+
121
+ # Order here is load-bearing: it's the order TA-Lib expects bundled
122
+ # price arrays to be given in (e.g. `atr([high, low, close], ...)`).
123
+ TA_FLAGS = {
124
+ TA_InputFlags: {
125
+ TA_IN_PRICE_OPEN: 0x00000001,
126
+ TA_IN_PRICE_HIGH: 0x00000002,
127
+ TA_IN_PRICE_LOW: 0x00000004,
128
+ TA_IN_PRICE_CLOSE: 0x00000008,
129
+ TA_IN_PRICE_VOLUME: 0x00000010,
130
+ TA_IN_PRICE_OPENINTEREST: 0x00000020,
131
+ TA_IN_PRICE_TIMESTAMP: 0x00000040
132
+ },
133
+ TA_OptInputFlags: {
134
+ TA_OPTIN_IS_PERCENT: 0x00100000,
135
+ TA_OPTIN_IS_DEGREE: 0x00200000,
136
+ TA_OPTIN_IS_CURRENCY: 0x00400000,
137
+ TA_OPTIN_ADVANCED: 0x01000000
138
+ },
139
+ TA_OutputFlags: {
140
+ TA_OUT_LINE: 0x00000001,
141
+ TA_OUT_DOT_LINE: 0x00000002,
142
+ TA_OUT_DASH_LINE: 0x00000004,
143
+ TA_OUT_DOT: 0x00000008,
144
+ TA_OUT_HISTO: 0x00000010,
145
+ TA_OUT_PATTERN_BOOL: 0x00000020,
146
+ TA_OUT_PATTERN_BULL_BEAR: 0x00000040,
147
+ TA_OUT_PATTERN_STRENGTH: 0x00000080,
148
+ TA_OUT_POSITIVE: 0x00000100,
149
+ TA_OUT_NEGATIVE: 0x00000200,
150
+ TA_OUT_ZERO: 0x00000400,
151
+ TA_OUT_UPPER_LIMIT: 0x00000800,
152
+ TA_OUT_LOWER_LIMIT: 0x00001000
153
+ }
154
+ }.freeze
155
+
156
+ extern "int TA_Initialize()"
157
+ extern "int TA_Shutdown()"
158
+ extern "int TA_GetFuncHandle(const char *name, const void **handle)"
159
+ extern "int TA_GetFuncInfo(const void *handle, const TA_FuncInfo **funcInfo)"
160
+ extern "int TA_GetInputParameterInfo(const void *handle, unsigned int paramIndex, const TA_InputParameterInfo **info)"
161
+ extern "int TA_GetOptInputParameterInfo(const void *handle, unsigned int paramIndex, const TA_OptInputParameterInfo **info)"
162
+ extern "int TA_GetOutputParameterInfo(const void *handle, unsigned int paramIndex, const TA_OutputParameterInfo **info)"
163
+ extern "int TA_ParamHolderAlloc(const void *handle, void **allocatedParams)"
164
+ extern "int TA_ParamHolderFree(void *params)"
165
+ extern "int TA_SetInputParamIntegerPtr(void *params, unsigned int paramIndex, const TA_Integer *value)"
166
+ extern "int TA_SetInputParamRealPtr(void *params, unsigned int paramIndex, const TA_Real *value)"
167
+ extern "int TA_SetInputParamPricePtr(void *params,
168
+ unsigned int paramIndex,
169
+ const TA_Real *open,
170
+ const TA_Real *high,
171
+ const TA_Real *low,
172
+ const TA_Real *close,
173
+ const TA_Real *volume,
174
+ const TA_Real *openInterest)"
175
+ extern "int TA_SetOptInputParamInteger(void *params, unsigned int paramIndex, TA_Integer optInValue)"
176
+ extern "int TA_SetOptInputParamReal(void *params, unsigned int paramIndex, TA_Real optInValue)"
177
+ extern "int TA_SetOutputParamIntegerPtr(void *params, unsigned int paramIndex, TA_Integer *out)"
178
+ extern "int TA_SetOutputParamRealPtr(void *params, unsigned int paramIndex, TA_Real *out)"
179
+ extern "int TA_GetLookback(const void *params, TA_Integer *lookback)"
180
+ extern "int TA_CallFunc(const void *params,
181
+ TA_Integer startIdx,
182
+ TA_Integer endIdx,
183
+ TA_Integer *outBegIdx,
184
+ TA_Integer *outNbElement)"
185
+
186
+ # Immutable snapshots of TA-Lib's introspection structs, built once
187
+ # per function name and memoized in @function_info_cache.
188
+ InputParam = Data.define(:type, :name, :flags)
189
+ OptInput = Data.define(:type, :name, :flags, :display_name, :default_value, :hint)
190
+ OutputParam = Data.define(:type, :name, :flags)
191
+ FuncInfo = Data.define(:name, :group, :hint, :handle, :inputs, :opt_inputs, :outputs) do
192
+ def price_input? = inputs.first&.type == TA_PARAM_TYPE[:TA_Input_Price]
193
+ end
194
+
195
+ class << self
196
+ # Looks up (and memoizes) a function's full signature via TA-Lib's
197
+ # abstract interface. Returns nil if `name` isn't a TA-Lib function
198
+ # (TA_GetFuncHandle matches case-insensitively).
199
+ def function_info(name)
200
+ (@function_info_cache ||= {}).fetch(name) { @function_info_cache[name] = build_function_info(name) }
201
+ end
202
+
203
+ def function_info_map = (@function_info_cache ||= {}).dup
204
+
205
+ def available? = !function_info("SMA").nil?
206
+
207
+ def method_missing(name, *args)
208
+ return super unless respond_to_missing?(name)
209
+
210
+ call_func(name.to_s, args)
211
+ end
212
+
213
+ # :reek:BooleanParameter -- include_private is Ruby's own required
214
+ # respond_to_missing? signature, not something this method chose.
215
+ def respond_to_missing?(name, include_private = false) = !function_info(name.to_s).nil? || super
216
+
217
+ private
218
+
219
+ def build_function_info(name)
220
+ handle_ptr = get_function_handle(name)
221
+ return nil unless handle_ptr
222
+
223
+ info_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
224
+ check_ta_return_code(TA_GetFuncInfo(handle_ptr, info_ptr.ref))
225
+ raw = TA_FuncInfo.new(info_ptr)
226
+
227
+ FuncInfo.new(
228
+ name: raw["name"].to_s,
229
+ group: raw["group"].to_s,
230
+ hint: raw["hint"].to_s,
231
+ handle: handle_ptr,
232
+ inputs: collect_inputs(handle_ptr, raw["nbInput"]),
233
+ opt_inputs: collect_opt_inputs(handle_ptr, raw["nbOptInput"]),
234
+ outputs: collect_outputs(handle_ptr, raw["nbOutput"])
235
+ )
236
+ end
237
+
238
+ def collect_inputs(handle_ptr, count)
239
+ collect_params(handle_ptr, count, TA_InputParameterInfo, method(:TA_GetInputParameterInfo)) do |raw_input|
240
+ InputParam.new(type: raw_input["type"], name: raw_input["paramName"].to_s, flags: raw_input["flags"])
241
+ end
242
+ end
243
+
244
+ def collect_opt_inputs(handle_ptr, count)
245
+ collect_params(handle_ptr, count, TA_OptInputParameterInfo, method(:TA_GetOptInputParameterInfo)) do |raw_opt|
246
+ OptInput.new(type: raw_opt["type"], name: raw_opt["paramName"].to_s, flags: raw_opt["flags"],
247
+ display_name: raw_opt["displayName"].to_s, default_value: raw_opt["defaultValue"], hint: raw_opt["hint"].to_s)
248
+ end
249
+ end
250
+
251
+ def collect_outputs(handle_ptr, count)
252
+ collect_params(handle_ptr, count, TA_OutputParameterInfo, method(:TA_GetOutputParameterInfo)) do |raw_output|
253
+ OutputParam.new(type: raw_output["type"], name: raw_output["paramName"].to_s, flags: raw_output["flags"])
254
+ end
255
+ end
256
+
257
+ def collect_params(handle_ptr, count, struct_type, getter)
258
+ count.times.map do |i|
259
+ ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
260
+ check_ta_return_code(getter.call(handle_ptr, i, ptr.ref))
261
+ yield struct_type.new(ptr)
262
+ end
263
+ end
264
+
265
+ def get_function_handle(name)
266
+ handle_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
267
+ ret_code = TA_GetFuncHandle(name, handle_ptr.ref)
268
+ return nil if ret_code == TA_FUNC_NOT_FOUND
269
+
270
+ check_ta_return_code(ret_code)
271
+ handle_ptr
272
+ end
273
+
274
+ # Calls a TA-Lib function.
275
+ #
276
+ # @param func_name [String]
277
+ # @param args [Array] positional input arrays (bundled as an
278
+ # array-of-arrays for TA_Input_Price parameters, e.g.
279
+ # `[[high, low, close]]`), optionally followed by a trailing
280
+ # Hash of optional parameters.
281
+ # @return [Array, Hash] a bare Array for single-output functions,
282
+ # a Hash keyed by snake_cased output name for multi-output ones.
283
+ def call_func(func_name, args)
284
+ options = extract_options!(args)
285
+ validate_inputs!(args)
286
+ info = function_info!(func_name)
287
+
288
+ with_parameter_holder(info.handle) do |params_ptr|
289
+ setup_input_parameters(params_ptr, args, info)
290
+ setup_optional_parameters(params_ptr, options, info)
291
+ calculate_lookback(params_ptr) # validates params before TA_CallFunc
292
+ calculate_results(params_ptr, args, info)
293
+ end
294
+ end
295
+
296
+ def extract_options!(args)
297
+ args.last.is_a?(Hash) ? args.pop : {}
298
+ end
299
+
300
+ def function_info!(func_name)
301
+ function_info(func_name) || raise(TALibError, "Unknown TA-Lib function: #{func_name}")
302
+ end
303
+
304
+ def with_parameter_holder(handle_ptr)
305
+ params_ptr = create_parameter_holder(handle_ptr)
306
+ yield params_ptr
307
+ ensure
308
+ TA_ParamHolderFree(params_ptr)
309
+ end
310
+
311
+ def validate_inputs!(arrays)
312
+ raise TALibError, "Input arrays cannot be empty" if arrays.empty?
313
+ raise TALibError, "Input must be arrays" unless arrays.all?(Array)
314
+ raise TALibError, "Input arrays cannot be empty" if arrays.any?(&:empty?)
315
+ raise TALibError, "Input arrays must contain only numbers" unless arrays.all? { |arr| arr.flatten.all?(Numeric) }
316
+ end
317
+
318
+ def create_parameter_holder(handle_ptr)
319
+ params_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP)
320
+ check_ta_return_code(TA_ParamHolderAlloc(handle_ptr, params_ptr.ref))
321
+ params_ptr
322
+ end
323
+
324
+ def calculate_lookback(params_ptr)
325
+ lookback_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
326
+ check_ta_return_code(TA_GetLookback(params_ptr, lookback_ptr))
327
+ lookback_ptr[0, Fiddle::SIZEOF_INT].unpack1("l")
328
+ end
329
+
330
+ def setup_input_parameters(params_ptr, input_arrays, info)
331
+ input_arrays.each_with_index do |array, index|
332
+ input = info.inputs[index]
333
+ raise TALibError, "#{info.name}: too many input arrays given" unless input
334
+
335
+ case input.type
336
+ when TA_PARAM_TYPE[:TA_Input_Price]
337
+ setup_price_inputs(params_ptr, index, array, input.flags)
338
+ when TA_PARAM_TYPE[:TA_Input_Real]
339
+ check_ta_return_code(TA_SetInputParamRealPtr(params_ptr, index, prepare_double_array(array)))
340
+ when TA_PARAM_TYPE[:TA_Input_Integer]
341
+ check_ta_return_code(TA_SetInputParamIntegerPtr(params_ptr, index, prepare_integer_array(array)))
342
+ end
343
+ end
344
+ end
345
+
346
+ def setup_price_inputs(params_ptr, index, price_data, flags)
347
+ required_flags = extract_flags(flags, :TA_InputFlags)
348
+ data_pointers = Array.new(6) { Fiddle::Pointer.malloc(0) }
349
+
350
+ required_flags.each_with_index do |flag, i|
351
+ array = price_data[i]
352
+ raise TALibError, "Missing price array for flag #{flag} at index #{i}" if array.nil?
353
+
354
+ data_pointers[TA_FLAGS[:TA_InputFlags].keys.index(flag)] = prepare_double_array(array)
355
+ end
356
+
357
+ check_ta_return_code(TA_SetInputParamPricePtr(params_ptr, index, *data_pointers))
358
+ end
359
+
360
+ def setup_optional_parameters(params_ptr, options, info)
361
+ info.opt_inputs.each_with_index do |opt_input, index|
362
+ param_name = normalize_parameter_name(opt_input.name).to_sym
363
+ next unless options.key?(param_name)
364
+
365
+ check_ta_return_code(set_optional_parameter(params_ptr, index, options[param_name], opt_input.type))
366
+ end
367
+ end
368
+
369
+ # :reek:ControlParameter -- dispatches on TA-Lib's own
370
+ # TA_OptInputParameterType tag (Real vs. Integer); something has
371
+ # to branch on that C-API classification somewhere.
372
+ def set_optional_parameter(params_ptr, index, value, type)
373
+ case type
374
+ when TA_PARAM_TYPE[:TA_OptInput_RealRange], TA_PARAM_TYPE[:TA_OptInput_RealList]
375
+ TA_SetOptInputParamReal(params_ptr, index, value)
376
+ when TA_PARAM_TYPE[:TA_OptInput_IntegerRange], TA_PARAM_TYPE[:TA_OptInput_IntegerList]
377
+ TA_SetOptInputParamInteger(params_ptr, index, value)
378
+ end
379
+ end
380
+
381
+ def prepare_double_array(array)
382
+ ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE * array.length)
383
+ ptr[0, Fiddle::SIZEOF_DOUBLE * array.length] = array.map(&:to_f).pack("d*")
384
+ ptr
385
+ end
386
+
387
+ def prepare_integer_array(array)
388
+ ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT * array.length)
389
+ ptr[0, Fiddle::SIZEOF_INT * array.length] = array.map(&:to_i).pack("l*")
390
+ ptr
391
+ end
392
+
393
+ def calculate_results(params_ptr, input_arrays, info)
394
+ input_size = info.price_input? ? input_arrays[0][0].length : input_arrays[0].length
395
+ output_ptrs = setup_output_buffers(params_ptr, input_size, info)
396
+
397
+ begin
398
+ actual_size = call_ta_func(params_ptr, input_size)
399
+ format_output_results(output_ptrs, actual_size, info)
400
+ ensure
401
+ output_ptrs.each(&:free)
402
+ end
403
+ end
404
+
405
+ def call_ta_func(params_ptr, input_size)
406
+ out_begin = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
407
+ out_size = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
408
+ check_ta_return_code(TA_CallFunc(params_ptr, 0, input_size - 1, out_begin, out_size))
409
+ out_size[0, Fiddle::SIZEOF_INT].unpack1("l")
410
+ ensure
411
+ out_begin.free
412
+ out_size.free
413
+ end
414
+
415
+ def setup_output_buffers(params_ptr, size, info)
416
+ info.outputs.each_with_index.map do |output, index|
417
+ ptr =
418
+ case output.type
419
+ when TA_PARAM_TYPE[:TA_Output_Real] then Fiddle::Pointer.malloc(Fiddle::SIZEOF_DOUBLE * size)
420
+ when TA_PARAM_TYPE[:TA_Output_Integer] then Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT * size)
421
+ end
422
+
423
+ ret_code =
424
+ case output.type
425
+ when TA_PARAM_TYPE[:TA_Output_Real] then TA_SetOutputParamRealPtr(params_ptr, index, ptr)
426
+ when TA_PARAM_TYPE[:TA_Output_Integer] then TA_SetOutputParamIntegerPtr(params_ptr, index, ptr)
427
+ end
428
+ check_ta_return_code(ret_code)
429
+
430
+ ptr
431
+ end
432
+ end
433
+
434
+ def format_output_results(output_ptrs, size, info)
435
+ results = output_ptrs.zip(info.outputs).map do |ptr, output|
436
+ case output.type
437
+ when TA_PARAM_TYPE[:TA_Output_Real] then ptr[0, Fiddle::SIZEOF_DOUBLE * size].unpack("d#{size}")
438
+ when TA_PARAM_TYPE[:TA_Output_Integer] then ptr[0, Fiddle::SIZEOF_INT * size].unpack("l#{size}")
439
+ end
440
+ end
441
+
442
+ return results.first if results.length == 1
443
+
444
+ info.outputs.map { normalize_parameter_name(it.name).to_sym }.zip(results).to_h
445
+ end
446
+
447
+ def extract_flags(value, type)
448
+ TA_FLAGS[type].select { |_k, v| value.anybits?(v) }.keys
449
+ end
450
+
451
+ def normalize_parameter_name(name)
452
+ name.sub(/^(optIn|outReal|outInteger|out|in)/, "")
453
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
454
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
455
+ .tr("-", "_")
456
+ .downcase
457
+ end
458
+
459
+ def check_ta_return_code(code)
460
+ return if code == TA_SUCCESS
461
+
462
+ raise TALibError, TA_ERROR_MESSAGES.fetch(code) { "Undefined TA-Lib error (Error code: #{code})" }
463
+ end
464
+ end
465
+
466
+ # Keyed by TA-Lib's raw TA_RetCode values (ta_common.h) — see the
467
+ # comment above TA_SUCCESS/TA_FUNC_NOT_FOUND for why these aren't
468
+ # also named constants.
469
+ TA_ERROR_MESSAGES = {
470
+ 1 => "TA-Lib not initialized, please call TA_Initialize first", # TA_LIB_NOT_INITIALIZE
471
+ 2 => "Bad parameter, please check input parameters", # TA_BAD_PARAM
472
+ 3 => "Memory allocation error, possibly insufficient memory", # TA_ALLOC_ERR
473
+ 4 => "Function group not found", # TA_GROUP_NOT_FOUND
474
+ 6 => "Invalid handle", # TA_INVALID_HANDLE
475
+ 7 => "Invalid parameter holder", # TA_INVALID_PARAM_HOLDER
476
+ 8 => "Invalid parameter holder type", # TA_INVALID_PARAM_HOLDER_TYPE
477
+ 9 => "Invalid parameter function", # TA_INVALID_PARAM_FUNCTION
478
+ 10 => "Input parameters not fully initialized", # TA_INPUT_NOT_ALL_INITIALIZE
479
+ 11 => "Output parameters not fully initialized", # TA_OUTPUT_NOT_ALL_INITIALIZE
480
+ 12 => "Start index out of range", # TA_OUT_OF_RANGE_START_INDEX
481
+ 13 => "End index out of range", # TA_OUT_OF_RANGE_END_INDEX
482
+ 14 => "Invalid list type", # TA_INVALID_LIST_TYPE
483
+ 15 => "Invalid object", # TA_BAD_OBJECT
484
+ 16 => "Operation not supported", # TA_NOT_SUPPORTED
485
+ 5000 => "TA-Lib internal error", # TA_INTERNAL_ERROR
486
+ 0xFFFF => "Unknown error", # TA_UNKNOWN_ERR
487
+ TA_FUNC_NOT_FOUND => "Function not found"
488
+ }.freeze
489
+
490
+ ret_code = TA_Initialize()
491
+ raise TALibError, "TA_Initialize failed (code #{ret_code})" unless ret_code == TA_SUCCESS
492
+
493
+ at_exit { TA_Shutdown() }
494
+ end
495
+ # rubocop:enable Metrics/ModuleLength
496
+ end
497
+ end
@@ -14,7 +14,7 @@ module SQA
14
14
  validate_prices!(prices)
15
15
  validate_period!(period, prices.size)
16
16
 
17
- TALibFFI.ma(prices, time_period: period, ma_type: ma_type)
17
+ Native.ma(prices, time_period: period, ma_type:)
18
18
  end
19
19
 
20
20
  # Simple Moving Average
@@ -26,7 +26,7 @@ module SQA
26
26
  validate_prices!(prices)
27
27
  validate_period!(period, prices.size)
28
28
 
29
- TALibFFI.sma(prices, time_period: period)
29
+ Native.sma(prices, time_period: period)
30
30
  end
31
31
 
32
32
  # Exponential Moving Average
@@ -38,7 +38,7 @@ module SQA
38
38
  validate_prices!(prices)
39
39
  validate_period!(period, prices.size)
40
40
 
41
- TALibFFI.ema(prices, time_period: period)
41
+ Native.ema(prices, time_period: period)
42
42
  end
43
43
 
44
44
  # Weighted Moving Average
@@ -50,7 +50,7 @@ module SQA
50
50
  validate_prices!(prices)
51
51
  validate_period!(period, prices.size)
52
52
 
53
- TALibFFI.wma(prices, time_period: period)
53
+ Native.wma(prices, time_period: period)
54
54
  end
55
55
 
56
56
  # Double Exponential Moving Average
@@ -62,7 +62,7 @@ module SQA
62
62
  validate_prices!(prices)
63
63
  validate_period!(period, prices.size)
64
64
 
65
- TALibFFI.dema(prices, time_period: period)
65
+ Native.dema(prices, time_period: period)
66
66
  end
67
67
 
68
68
  # Triple Exponential Moving Average
@@ -74,7 +74,7 @@ module SQA
74
74
  validate_prices!(prices)
75
75
  validate_period!(period, prices.size)
76
76
 
77
- TALibFFI.tema(prices, time_period: period)
77
+ Native.tema(prices, time_period: period)
78
78
  end
79
79
 
80
80
  # Triangular Moving Average
@@ -86,7 +86,7 @@ module SQA
86
86
  validate_prices!(prices)
87
87
  validate_period!(period, prices.size)
88
88
 
89
- TALibFFI.trima(prices, time_period: period)
89
+ Native.trima(prices, time_period: period)
90
90
  end
91
91
 
92
92
  # Kaufman Adaptive Moving Average
@@ -98,7 +98,7 @@ module SQA
98
98
  validate_prices!(prices)
99
99
  validate_period!(period, prices.size)
100
100
 
101
- TALibFFI.kama(prices, time_period: period)
101
+ Native.kama(prices, time_period: period)
102
102
  end
103
103
 
104
104
  # Triple Exponential Moving Average (T3)
@@ -106,12 +106,14 @@ module SQA
106
106
  # @param period [Integer] Time period (default: 5)
107
107
  # @param vfactor [Float] Volume factor (default: 0.7)
108
108
  # @return [Array<Float>] T3 values
109
+ # :reek:UncommunicativeMethodName -- T3 is TA-Lib's own canonical
110
+ # indicator name; renaming it breaks the public API.
109
111
  def t3(prices, period: 5, vfactor: 0.7)
110
112
  check_available!
111
113
  validate_prices!(prices)
112
114
  validate_period!(period, prices.size)
113
115
 
114
- TALibFFI.t3(prices, time_period: period, vfactor: vfactor)
116
+ Native.t3(prices, time_period: period, vfactor:)
115
117
  end
116
118
 
117
119
  # Bollinger Bands
@@ -125,19 +127,8 @@ module SQA
125
127
  validate_prices!(prices)
126
128
  validate_period!(period, prices.size)
127
129
 
128
- result = TALibFFI.bbands(
129
- prices,
130
- time_period: period,
131
- nbdev_up: nbdev_up,
132
- nbdev_dn: nbdev_down
133
- )
134
-
135
- # Handle hash return format from newer ta_lib_ffi versions
136
- if result.is_a?(Hash)
137
- [result[:upper_band], result[:middle_band], result[:lower_band]]
138
- else
139
- result
140
- end
130
+ result = Native.bbands(prices, time_period: period, nbdev_up:, nbdev_dn: nbdev_down)
131
+ [result[:upper_band], result[:middle_band], result[:lower_band]]
141
132
  end
142
133
  end
143
134
  end