1+ # frozen_string_literal: true
2+
13require "fiddle"
24require "fiddle/import"
35
6+ # Ruby FFI wrapper for TA-Lib (Technical Analysis Library)
47module TALib
5- VERSION = "0.1.0" . freeze
8+ VERSION = "0.1.0"
69
710 extend Fiddle ::Importer
811
@@ -12,7 +15,7 @@ module TALib
1215 "#{ brew_prefix } /lib/libta-lib.dylib"
1316 when /linux/
1417 "libta-lib.so"
15- when /win32|mingw32 /
18+ when /cygwin|mswin|mingw|bccwin|wince|emx /
1619 "C:/Program Files/TA-Lib/bin/ta-lib.dll"
1720 else
1821 raise "Unsupported platform"
@@ -42,6 +45,16 @@ class TALibError < StandardError; end
4245 TA_INTERNAL_ERROR = 5000
4346 TA_UNKNOWN_ERR = 0xFFFF
4447
48+ # {0,"SMA"},
49+ # {1,"EMA"},
50+ # {2,"WMA"},
51+ # {3,"DEMA" },
52+ # {4,"TEMA" },
53+ # {5,"TRIMA"},
54+ # {6,"KAMA" },
55+ # {7,"MAMA" },
56+ # {8,"T3"}
57+
4558 typealias "TA_Real" , "double"
4659 typealias "TA_Integer" , "int"
4760 typealias "TA_RetCode" , "int"
@@ -100,21 +113,21 @@ class TALibError < StandardError; end
100113 "TA_OutputFlags flags"
101114 ]
102115
103- TA_Input_Price = 0
104- TA_Input_Real = 1
105- TA_Input_Integer = 2
106-
107- TA_OptInput_RealRange = 0
108- TA_OptInput_RealList = 1
109- TA_OptInput_IntegerRange = 2
110- TA_OptInput_IntegerList = 3
111-
112- TA_Output_Real = 0
113- TA_Output_Integer = 1
116+ TA_PARAM_TYPE = {
117+ TA_Input_Price : 0 ,
118+ TA_Input_Real : 1 ,
119+ TA_Input_Integer : 2 ,
120+ TA_OptInput_RealRange : 0 ,
121+ TA_OptInput_RealList : 1 ,
122+ TA_OptInput_IntegerRange : 2 ,
123+ TA_OptInput_IntegerList : 3 ,
124+ TA_Output_Real : 0 ,
125+ TA_Output_Integer : 1
126+ } . freeze
114127
115128 TA_FLAGS = {
116129 TA_InputFlags : {
117- TA_InputFlags : 0x00000001 ,
130+ TA_IN_PRICE_OPEN : 0x00000001 ,
118131 TA_IN_PRICE_HIGH : 0x00000002 ,
119132 TA_IN_PRICE_LOW : 0x00000004 ,
120133 TA_IN_PRICE_CLOSE : 0x00000008 ,
@@ -143,9 +156,8 @@ class TALibError < StandardError; end
143156 TA_OUT_UPPER_LIMIT : 0x00000800 ,
144157 TA_OUT_LOWER_LIMIT : 0x00001000
145158 }
146- }
159+ } . freeze
147160
148- extern "const char *TA_GetVersionString(void)"
149161 extern "int TA_Initialize()"
150162 extern "int TA_Shutdown()"
151163 extern "int TA_GroupTableAlloc(TA_StringTable**)"
@@ -192,10 +204,6 @@ def extract_flags(value, type)
192204 flags_set
193205 end
194206
195- def ta_lib_version
196- TA_GetVersionString ( ) . to_s
197- end
198-
199207 def group_table
200208 string_table_ptr = Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_VOIDP )
201209 ret_code = TA_GroupTableAlloc ( string_table_ptr . ref )
@@ -246,6 +254,8 @@ def each_function(&block)
246254 check_ta_return_code ( ret_code )
247255 end
248256
257+ # rubocop:disable Metrics/MethodLength
258+ # rubocop:disable Metrics/AbcSize
249259 def print_function_info ( func_info )
250260 puts "Function Name: #{ func_info [ "name" ] } "
251261 puts "Function Group: #{ func_info [ "group" ] } "
@@ -263,7 +273,7 @@ def print_function_info(func_info)
263273 ret_code = TA_GetInputParameterInfo ( func_info [ "handle" ] , i , param_info_ptr . ref )
264274 check_ta_return_code ( ret_code )
265275 param_info = TA_InputParameterInfo . new ( param_info_ptr )
266- puts " Parameter #{ i + 1 } :"
276+ puts " Parameter #{ i + 1 } :"
267277 puts " Name: #{ param_info [ "paramName" ] } "
268278 puts " Type: #{ param_info [ "type" ] } "
269279 puts " Flags: #{ extract_flags ( param_info [ "flags" ] , :TA_InputFlags ) } "
@@ -296,7 +306,10 @@ def print_function_info(func_info)
296306 puts " Flags: #{ extract_flags ( param_info [ "flags" ] , :TA_OutputFlags ) } "
297307 end
298308 end
309+ # rubocop:enable Metrics/MethodLength
310+ # rubocop:enable Metrics/AbcSize
299311
312+ # rubocop:disable Metrics/MethodLength
300313 def call_func ( func_name , args )
301314 options = args . last . is_a? ( Hash ) ? args . pop : { }
302315 input_arrays = args
@@ -316,6 +329,7 @@ def call_func(func_name, args)
316329 TA_ParamHolderFree ( params_ptr )
317330 end
318331 end
332+ # rubocop:enable Metrics/MethodLength
319333
320334 def calculate_lookback ( params_ptr )
321335 lookback_ptr = Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_INT )
@@ -324,6 +338,8 @@ def calculate_lookback(params_ptr)
324338 lookback_ptr [ 0 , Fiddle ::SIZEOF_INT ] . unpack1 ( "l" )
325339 end
326340
341+ # rubocop:disable Metrics/CyclomaticComplexity
342+ # rubocop:disable Metrics/PerceivedComplexity
327343 def validate_inputs! ( arrays )
328344 raise TALibError , "Input arrays cannot be empty" if arrays . empty?
329345
@@ -332,13 +348,14 @@ def validate_inputs!(arrays)
332348 end
333349
334350 sizes = arrays . map ( &:length )
335- raise TALibError , "All input arrays must have the same length" unless sizes . uniq . length == 1
336- raise TALibError , "Input arrays cannot be empty" if sizes . first . zero?
351+ raise TALibError , "Input arrays cannot be empty" if sizes . any? ( &:zero? )
337352
338353 arrays . each do |arr |
339354 raise TALibError , "Input arrays must contain only numbers" unless arr . flatten . all? { |x | x . is_a? ( Numeric ) }
340355 end
341356 end
357+ # rubocop:enable Metrics/CyclomaticComplexity
358+ # rubocop:enable Metrics/PerceivedComplexity
342359
343360 def get_function_handle ( func_name )
344361 handle_ptr = Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_VOIDP )
@@ -365,13 +382,13 @@ def setup_input_parameters(params_ptr, input_arrays, func_name)
365382
366383 def set_input_parameter ( params_ptr , index , array , input_info )
367384 case input_info [ "type" ]
368- when TA_Input_Real
385+ when TA_PARAM_TYPE [ : TA_Input_Real]
369386 input_ptr = prepare_double_array ( array )
370387 TA_SetInputParamRealPtr ( params_ptr , index , input_ptr )
371- when TA_Input_Integer
388+ when TA_PARAM_TYPE [ : TA_Input_Integer]
372389 input_ptr = prepare_integer_array ( array )
373390 TA_SetInputParamIntegerPtr ( params_ptr , index , input_ptr )
374- when TA_Input_Price
391+ when TA_PARAM_TYPE [ : TA_Input_Price]
375392 setup_price_inputs ( params_ptr , index , array , input_info [ "flags" ] )
376393 end
377394 end
@@ -402,14 +419,15 @@ def setup_optional_parameters(params_ptr, options, func_name)
402419
403420 def set_optional_parameter ( params_ptr , index , value , type )
404421 case type
405- when TA_OptInput_RealRange , TA_OptInput_RealList
422+ when TA_PARAM_TYPE [ : TA_OptInput_RealRange] , TA_PARAM_TYPE [ : TA_OptInput_RealList]
406423 ret_code = TA_SetOptInputParamReal ( params_ptr , index , value )
407- when TA_OptInput_IntegerRange , TA_OptInput_IntegerList
424+ when TA_PARAM_TYPE [ : TA_OptInput_IntegerRange] , TA_PARAM_TYPE [ : TA_OptInput_IntegerList]
408425 ret_code = TA_SetOptInputParamInteger ( params_ptr , index , value )
409426 end
410427 check_ta_return_code ( ret_code )
411428 end
412429
430+ # rubocop:disable Metrics/MethodLength
413431 def calculate_results ( params_ptr , input_size , func_name )
414432 out_begin = Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_INT )
415433 out_size = Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_INT )
@@ -420,32 +438,36 @@ def calculate_results(params_ptr, input_size, func_name)
420438 check_ta_return_code ( ret_code )
421439
422440 actual_size = out_size [ 0 , Fiddle ::SIZEOF_INT ] . unpack1 ( "l" )
441+ puts "actual_size: #{ actual_size } "
423442 format_output_results ( output_arrays , actual_size , func_name )
424443 ensure
425444 out_begin . free
426445 out_size . free
427446 output_arrays . each ( &:free )
428447 end
429448 end
449+ # rubocop:enable Metrics/MethodLength
430450
451+ # rubocop:disable Metrics/MethodLength
452+ # rubocop:disable Metrics/AbcSize
431453 def setup_output_buffers ( params_ptr , size , func_name )
432454 func_info = function_info_map [ func_name ]
433455 output_ptrs = [ ]
434456
435457 func_info [ :outputs ] . each_with_index do |output , index |
436458 ptr = case output [ "type" ]
437- when TA_Output_Real
459+ when TA_PARAM_TYPE [ : TA_Output_Real]
438460 Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_DOUBLE * size )
439- when TA_Output_Integer
461+ when TA_PARAM_TYPE [ : TA_Output_Integer]
440462 Fiddle ::Pointer . malloc ( Fiddle ::SIZEOF_INT * size )
441463 end
442464
443465 output_ptrs << ptr
444466
445467 ret_code = case output [ "type" ]
446- when TA_Output_Real
468+ when TA_PARAM_TYPE [ : TA_Output_Real]
447469 TA_SetOutputParamRealPtr ( params_ptr , index , ptr )
448- when TA_Output_Integer
470+ when TA_PARAM_TYPE [ : TA_Output_Integer]
449471 TA_SetOutputParamIntegerPtr ( params_ptr , index , ptr )
450472 end
451473
@@ -454,14 +476,18 @@ def setup_output_buffers(params_ptr, size, func_name)
454476
455477 output_ptrs
456478 end
479+ # rubocop:enable Metrics/MethodLength
480+ # rubocop:enable Metrics/AbcSize
457481
482+ # rubocop:disable Metrics/MethodLength
483+ # rubocop:disable Metrics/AbcSize
458484 def format_output_results ( output_ptrs , size , func_name )
459485 func_info = function_info_map [ func_name ]
460486 results = output_ptrs . zip ( func_info [ :outputs ] ) . map do |ptr , output |
461487 case output [ "type" ]
462- when TA_Output_Real
488+ when TA_PARAM_TYPE [ : TA_Output_Real]
463489 ptr [ 0 , Fiddle ::SIZEOF_DOUBLE * size ] . unpack ( "d#{ size } " )
464- when TA_Output_Integer
490+ when TA_PARAM_TYPE [ : TA_Output_Integer]
465491 ptr [ 0 , Fiddle ::SIZEOF_INT * size ] . unpack ( "l#{ size } " )
466492 end
467493 end
@@ -473,6 +499,8 @@ def format_output_results(output_ptrs, size, func_name)
473499 end
474500 output_names . zip ( results ) . to_h
475501 end
502+ # rubocop:enable Metrics/AbcSize
503+ # rubocop:enable Metrics/MethodLength
476504
477505 def function_description_xml
478506 TA_FunctionDescriptionXML ( ) . to_s
@@ -534,6 +562,9 @@ def normalize_parameter_name(name)
534562 . downcase
535563 end
536564
565+ # rubocop:disable Metrics/MethodLength
566+ # rubocop:disable Metrics/AbcSize
567+ # rubocop:disable Metrics/CyclomaticComplexity
537568 def check_ta_return_code ( code )
538569 return if code == TA_SUCCESS
539570
@@ -580,6 +611,9 @@ def check_ta_return_code(code)
580611
581612 raise TALibError , error_message
582613 end
614+ # rubocop:enable Metrics/CyclomaticComplexity
615+ # rubocop:enable Metrics/MethodLength
616+ # rubocop:enable Metrics/AbcSize
583617
584618 def initialize_ta_lib
585619 return if @initialized
@@ -598,23 +632,13 @@ def define_ta_function(method_name, func_name)
598632
599633 def setup_price_inputs ( params_ptr , index , price_data , flags )
600634 required_flags = extract_flags ( flags , :TA_InputFlags )
601- flag_to_index = {
602- TA_IN_PRICE_OPEN : 0 ,
603- TA_IN_PRICE_HIGH : 1 ,
604- TA_IN_PRICE_LOW : 2 ,
605- TA_IN_PRICE_CLOSE : 3 ,
606- TA_IN_PRICE_VOLUME : 4 ,
607- TA_IN_PRICE_OPENINTEREST : 5
608- }
609-
610635 data_pointers = Array . new ( 6 ) { nil }
611-
612- flag_to_index . each_key do |flag |
613- data_pointers [ flag_to_index [ flag ] ] = if required_flags . include? ( flag )
614- prepare_double_array ( price_data [ required_flags . index ( flag ) ] )
615- else
616- Fiddle ::Pointer . malloc ( 0 )
617- end
636+ TA_FLAGS [ :TA_InputFlags ] . keys [ 0 ..5 ] . each_with_index do |flag , i |
637+ data_pointers [ i ] = if required_flags . include? ( flag )
638+ prepare_double_array ( price_data [ required_flags . index ( flag ) ] )
639+ else
640+ Fiddle ::Pointer . malloc ( 0 )
641+ end
618642 end
619643
620644 TA_SetInputParamPricePtr ( params_ptr , index , *data_pointers )
@@ -623,50 +647,3 @@ def setup_price_inputs(params_ptr, index, price_data, flags)
623647 initialize_ta_lib
624648 generate_ta_functions
625649end
626-
627- # puts TALib.group_table
628- # puts TALib.function_table("Math Operators")
629- # puts TALib.function_table("Math Transform")
630- # puts TALib.function_info(TALib.function_info("BBANDS"))
631- # TALib.each_function do |func_info|
632- # # puts func_info[]
633- # end
634- # prices = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0]
635- # result = TALib.call_func("SMA", [prices, { time_period: 5 }])
636- # puts "开始索引: #{result[:begin_idx]}"
637- # puts "SMA结果: #{result[:data].inspect}"
638- # puts TALib.function_description_xml
639- # puts TALib.function_info_map
640- # 计算简单移动平均线
641- # prices = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0]
642- # sma = TALib.bbands(prices, time_period: 3)
643- # puts "SMA: #{sma}"
644-
645- # # # 计算 MACD
646- # puts TALib.print_function_info(TALib.function_info("MACD"))
647- # macd = TALib.macd(prices,
648- # fast_period: 3,
649- # slow_period: 2,
650- # signal_period: 1)
651- # puts "MACD: #{macd}"
652-
653- # puts TALib.print_function_info(TALib.function_info("EMA"))
654- # prices = Array.new(100) { rand(100) }
655- # # 调用 EMA
656-
657- # prices = [1, 1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5]
658- # puts TALib.ema(prices, time_period: 5)
659-
660- # require "tulirb"
661- # puts Tulirb.ema([prices], period: 5)
662-
663- # puts ema2 == ema1[-ema1.size..-1]
664- # puts ema2.join(",")
665- # puts ema1.join(",")
666-
667- # prices = Array.new(100) { rand(100) }
668- # puts TALib.print_function_info(TALib.function_info("MA"))
669- # puts TALib.sma(prices, time_period: 5).join(",")
670- # puts "XXXXXXXXXXXXXX"
671- # puts Tulirb.sma([prices], period: 5).join(",")
672- # puts TALib.sma(prices, time_period: 5).map { |x| x.round(2) } == Tulirb.sma([prices], period: 5).first.map { |x| x.round(2) }
0 commit comments