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.
- checksums.yaml +4 -4
- data/.claude/settings.local.json +33 -0
- data/.envrc +6 -1
- data/.loki +10 -0
- data/.quality/reek_baseline.txt +0 -0
- data/.rubocop.yml +221 -0
- data/CHANGELOG.md +30 -2
- data/Gemfile +0 -8
- data/Gemfile.local +19 -0
- data/README.md +9 -10
- data/Rakefile +163 -7
- data/lib/sqa/tai/cycle_indicators.rb +7 -19
- data/lib/sqa/tai/help/resource.rb +4 -3
- data/lib/sqa/tai/help.rb +16 -11
- data/lib/sqa/tai/momentum_indicators.rb +51 -110
- data/lib/sqa/tai/native.rb +497 -0
- data/lib/sqa/tai/overlap_studies.rb +13 -22
- data/lib/sqa/tai/pattern_recognition.rb +67 -61
- data/lib/sqa/tai/price_transform.rb +10 -4
- data/lib/sqa/tai/statistical_functions.rb +21 -21
- data/lib/sqa/tai/version.rb +1 -1
- data/lib/sqa/tai/volatility_indicators.rb +27 -31
- data/lib/sqa/tai/volume_indicators.rb +9 -3
- data/lib/sqa/tai.rb +38 -29
- data/mkdocs.yml +153 -15
- data/sqa-tai.gemspec +54 -0
- metadata +131 -13
- data/Gemfile.lock +0 -71
- data/lib/extensions/ta_lib_ffi.rb +0 -154
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
129
|
-
|
|
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
|