sqa-tai 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/.envrc +1 -0
- data/CHANGELOG.md +34 -0
- data/COMMITS.md +196 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +71 -0
- data/LICENSE +21 -0
- data/README.md +290 -0
- data/Rakefile +13 -0
- data/lib/extensions/ta_lib_ffi.rb +154 -0
- data/lib/sqa/tai/version.rb +7 -0
- data/lib/sqa/tai.rb +1850 -0
- data/mkdocs.yml +179 -0
- data/ta-lib-license.md +26 -0
- metadata +158 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Monkey patch for ta_lib_ffi 0.3.0 to fix multi-array parameter bug
|
|
4
|
+
#
|
|
5
|
+
# This patch fixes a critical bug in ta_lib_ffi 0.3.0 where functions requiring
|
|
6
|
+
# multiple array parameters (high, low, close) fail with NoMethodError.
|
|
7
|
+
#
|
|
8
|
+
# Affected functions:
|
|
9
|
+
# - All volatility indicators (ATR, NATR, SAR, TRANGE)
|
|
10
|
+
# - All momentum indicators requiring OHLC (CCI, WILLR, ADX, STOCH)
|
|
11
|
+
# - All volume indicators (OBV, AD, ADOSC)
|
|
12
|
+
# - All candlestick pattern recognition functions (60+ patterns)
|
|
13
|
+
#
|
|
14
|
+
# This patch can be removed once ta_lib_ffi fixes the upstream bug.
|
|
15
|
+
|
|
16
|
+
module TALibFFI
|
|
17
|
+
class << self
|
|
18
|
+
# Store original method to call it for non-price parameters
|
|
19
|
+
alias_method :original_setup_input_parameters, :setup_input_parameters
|
|
20
|
+
|
|
21
|
+
# Fixed version that properly handles multi-array Price inputs
|
|
22
|
+
#
|
|
23
|
+
# The original code assumes a 1-to-1 mapping between input_arrays and function
|
|
24
|
+
# parameters, but TA_Input_Price parameters can consume multiple arrays.
|
|
25
|
+
#
|
|
26
|
+
# This fix:
|
|
27
|
+
# 1. Tracks position in input_arrays with array_index
|
|
28
|
+
# 2. For each function parameter, checks its type
|
|
29
|
+
# 3. For TA_Input_Price, determines how many arrays are needed from flags
|
|
30
|
+
# 4. Bundles those arrays together: [[high], [low], [close]]
|
|
31
|
+
# 5. Passes the bundle to setup_price_inputs
|
|
32
|
+
# 6. Advances array_index by the number of arrays consumed
|
|
33
|
+
def setup_input_parameters(params_ptr, input_arrays, func_name)
|
|
34
|
+
func_info = function_info_map[func_name]
|
|
35
|
+
array_index = 0 # Track position in input_arrays
|
|
36
|
+
|
|
37
|
+
func_info[:inputs].each_with_index do |input_info, param_index|
|
|
38
|
+
case input_info["type"]
|
|
39
|
+
when TA_PARAM_TYPE[:TA_Input_Price]
|
|
40
|
+
# Price inputs consume multiple arrays based on required flags
|
|
41
|
+
required_flags = extract_flags(input_info["flags"], :TA_InputFlags)
|
|
42
|
+
num_arrays = required_flags.length
|
|
43
|
+
|
|
44
|
+
# Collect the next num_arrays from input_arrays
|
|
45
|
+
if array_index + num_arrays > input_arrays.length
|
|
46
|
+
raise ArgumentError, "Function #{func_name} requires #{num_arrays} price arrays " \
|
|
47
|
+
"(#{required_flags.join(', ')}), but only #{input_arrays.length - array_index} " \
|
|
48
|
+
"provided at position #{array_index}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
price_arrays = input_arrays[array_index, num_arrays]
|
|
52
|
+
|
|
53
|
+
# Pass the bundled arrays to set_input_parameter
|
|
54
|
+
ret_code = set_input_parameter(params_ptr, param_index, price_arrays, input_info)
|
|
55
|
+
check_ta_return_code(ret_code)
|
|
56
|
+
|
|
57
|
+
array_index += num_arrays # Advance by number of arrays consumed
|
|
58
|
+
|
|
59
|
+
when TA_PARAM_TYPE[:TA_Input_Real], TA_PARAM_TYPE[:TA_Input_Integer]
|
|
60
|
+
# Single array inputs
|
|
61
|
+
if array_index >= input_arrays.length
|
|
62
|
+
raise ArgumentError, "Not enough input arrays for function #{func_name} " \
|
|
63
|
+
"(expected array at index #{array_index}, but only #{input_arrays.length} provided)"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
ret_code = set_input_parameter(params_ptr, param_index, input_arrays[array_index], input_info)
|
|
67
|
+
check_ta_return_code(ret_code)
|
|
68
|
+
|
|
69
|
+
array_index += 1 # Advance by one
|
|
70
|
+
|
|
71
|
+
else
|
|
72
|
+
# Unknown type - should not happen, but handle gracefully
|
|
73
|
+
raise ArgumentError, "Unknown input type #{input_info['type']} for function #{func_name}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Verify all arrays were consumed
|
|
78
|
+
if array_index != input_arrays.length
|
|
79
|
+
raise ArgumentError, "Function #{func_name} expected #{array_index} input arrays " \
|
|
80
|
+
"but received #{input_arrays.length}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Store original method
|
|
85
|
+
alias_method :original_setup_price_inputs, :setup_price_inputs
|
|
86
|
+
|
|
87
|
+
# Fixed version that handles bundled price arrays correctly
|
|
88
|
+
#
|
|
89
|
+
# After the setup_input_parameters fix, price_data is now guaranteed to be
|
|
90
|
+
# an array of arrays: [[high_array], [low_array], [close_array]]
|
|
91
|
+
def setup_price_inputs(params_ptr, index, price_data, flags)
|
|
92
|
+
required_flags = extract_flags(flags, :TA_InputFlags)
|
|
93
|
+
data_pointers = Array.new(6) { Fiddle::Pointer.malloc(0) }
|
|
94
|
+
|
|
95
|
+
# price_data is now an array of arrays: [[high], [low], [close]]
|
|
96
|
+
# Each element corresponds to one required flag
|
|
97
|
+
required_flags.each_with_index do |flag, i|
|
|
98
|
+
flag_index = TA_FLAGS[:TA_InputFlags].keys.index(flag)
|
|
99
|
+
|
|
100
|
+
# price_data[i] now correctly returns an array (e.g., high, low, or close)
|
|
101
|
+
if price_data[i].nil?
|
|
102
|
+
raise ArgumentError, "Missing price array for flag #{flag} at index #{i}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Validate it's an array
|
|
106
|
+
unless price_data[i].is_a?(Array)
|
|
107
|
+
raise ArgumentError, "Expected array for flag #{flag} at index #{i}, " \
|
|
108
|
+
"got #{price_data[i].class}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
data_pointers[flag_index] = prepare_double_array(price_data[i]) if required_flags.include?(flag)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
TA_SetInputParamPricePtr(params_ptr, index, *data_pointers)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Store original method
|
|
118
|
+
alias_method :original_calculate_results, :calculate_results
|
|
119
|
+
|
|
120
|
+
# Fixed version that correctly determines input_size for Price inputs
|
|
121
|
+
#
|
|
122
|
+
# The original code expects input_arrays[0][0].length for Price inputs,
|
|
123
|
+
# which assumes format [[high], [low], [close]]. Our fix passes [high, low, close],
|
|
124
|
+
# so we need to adjust this check.
|
|
125
|
+
def calculate_results(params_ptr, input_arrays, func_name)
|
|
126
|
+
func_info = function_info_map[func_name]
|
|
127
|
+
|
|
128
|
+
# Determine input size based on input type
|
|
129
|
+
input_size = if func_info[:inputs].first["type"] == TA_PARAM_TYPE[:TA_Input_Price]
|
|
130
|
+
# For Price inputs, input_arrays is [high, low, close]
|
|
131
|
+
# Get length from the first array
|
|
132
|
+
input_arrays[0].length
|
|
133
|
+
else
|
|
134
|
+
# For other inputs, get length normally
|
|
135
|
+
input_arrays[0].length
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
out_begin = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
|
|
139
|
+
out_size = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
|
|
140
|
+
output_arrays = setup_output_buffers(params_ptr, input_size, func_name)
|
|
141
|
+
|
|
142
|
+
begin
|
|
143
|
+
ret_code = TA_CallFunc(params_ptr, 0, input_size - 1, out_begin, out_size)
|
|
144
|
+
check_ta_return_code(ret_code)
|
|
145
|
+
|
|
146
|
+
actual_size = out_size[0, Fiddle::SIZEOF_INT].unpack1("l")
|
|
147
|
+
format_output_results(output_arrays, actual_size, func_name)
|
|
148
|
+
ensure
|
|
149
|
+
out_begin.free
|
|
150
|
+
out_size.free
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|