ib-technical-analysis 0.2

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,11 @@
1
+ require 'logging.rb'
2
+ require 'ib/bar.rb'
3
+ require 'technical_analysis/moving-average/base.rb'
4
+ require 'technical_analysis/moving-average/simple-ma.rb'
5
+ require 'technical_analysis/moving-average/weighted-ma.rb'
6
+ require 'technical_analysis/moving-average/exp-ma.rb'
7
+ require 'technical_analysis/moving-average/ka-ma.rb'
8
+ require 'technical_analysis/momentum/tsi.rb'
9
+ require 'technical_analysis/momentum/lane-stochastic.rb'
10
+ require 'technical_analysis/momentum/rsi.rb'
11
+ require 'ta_support'
@@ -0,0 +1,4 @@
1
+ module TechnicalAnalysis
2
+ module BarCalculation
3
+
4
+ end
@@ -0,0 +1,46 @@
1
+ module TechnicalAnalysis
2
+
3
+ module Momentum
4
+ RSI = Struct.new :time, :value
5
+ TSI = Struct.new :time, :value
6
+
7
+ #
8
+ # Calculates the true strength index (TSI) for the data over the given period
9
+ # https://en.wikipedia.org/wiki/True_strength_index
10
+
11
+ #
12
+ # z = Symbols::Futures.mini_dax.eod( duration: '30 d').each
13
+ # e= nil
14
+ # ema= z.map{|y| e= TechnicalAnalysis::MovingAverage.ema( y.close, z.map(&:close), 30, e ) }
15
+ #
16
+ # or
17
+ #
18
+ # EMA = Struct.new :time, :ema
19
+ # e = nil
20
+ # ema = z.map do |y|
21
+ # EMA.new y.time,
22
+ # e = TechnicalAnalysis::MovingAverage.ema y.close, z.map(&:close), 30, e
23
+ # end
24
+ #
25
+
26
+ def self.tsi current_value, tsi_source, low_period = 13, high_period = 25
27
+
28
+ if current_value.present?
29
+ tsi_source.push current_value
30
+ else
31
+ current_value = tsi_source.last
32
+ end
33
+
34
+ momentum = tsi_source[-1] - tsi_source[-2]
35
+
36
+ high_multiplier = (2.0 / (high_period + 1.0))
37
+ low_multiplier = (2.0 / (low_period + 1.0))
38
+
39
+ if prev_tsi.nil?
40
+ data.sum / data.size.to_f # Average
41
+ else
42
+ (current_value - prev_ema) * (2.0 / (period + 1.0)) + prev_ema
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,105 @@
1
+ module TechnicalAnalysis
2
+ module Momentum
3
+ LANE = Struct.new :time, :value
4
+
5
+
6
+ =begin rdoc
7
+ Defines the Lane-Stochastics
8
+ price - min(low)[period]
9
+ %k = -----------------------------------------------
10
+ max(high)[period] - min(low)[period]
11
+
12
+ The Stochastics may depend on
13
+ * OHLC-Objects (IB::Bar).
14
+
15
+ Then <em>"close"</em> is taken as default-value of the current candle.
16
+
17
+ By specifiying the parameter <em>"take"</em> this can be customized.
18
+
19
+ =end
20
+ class Lane
21
+ # attr :oversold, true
22
+ # attr :overbought, true
23
+
24
+ def initialize period: 5, fast: 3, slow: 3, data: [], strict: true, take: :close
25
+ # note: strict is always true
26
+ raise "Periods must be greater then one" if period <= 1
27
+ raise "fast must be smaller then slow" if slow < fast
28
+
29
+ @queue = []
30
+ @buffer = []
31
+ @take = take
32
+ @smooth_2 = slow # smooth-Const of StochasticsSlow
33
+ @smooth_1 = fast # smooth-Const of StochasticsFast
34
+ @period = period # period of StochasticsLane
35
+ @stochastics_fast = TechnicalAnalysis::MovingAverage::ExpMA.new( period: @smooth_1, strict: true)
36
+ @stochastics_slow = TechnicalAnalysis::MovingAverage::ExpMA.new( period: @smooth_2, strict: true)
37
+ if !data.empty?
38
+ data.map{|d| add_item d }
39
+ end
40
+
41
+ end
42
+
43
+
44
+ # adds item, calculates the ema, puts value to the buffer and returns the result
45
+ def add_item value
46
+ is_ohlc = nil
47
+ @queue << if value.is_a? IB::Bar
48
+ is_ohlc = true
49
+ value
50
+ else
51
+ value.to_f
52
+ end
53
+
54
+ if @queue.size < @period
55
+ @buffer << nil
56
+ else
57
+ @queue.shift if @queue.size > @period
58
+ the_high = is_ohlc ? @queue.map(&:high).max : @queue.max
59
+ the_low = is_ohlc ? @queue.map(&:low).min : @queue.min
60
+ # the value is :close by default. But can be :wap, :typical_price aso.
61
+ the_value = is_ohlc ? value.send( @take ) : value
62
+ k = ( the_value - the_low )/ ( the_high - the_low )
63
+ @stochastics_fast.add_item k
64
+
65
+ out_1 = if @stochastics_fast.current.present?
66
+ @stochastics_slow.add_item @stochastics_fast.current
67
+ @stochastics_fast.current
68
+ else
69
+ nil
70
+ end
71
+
72
+ #puts "momentum: #{@emas[:low].current} <--> #{@emas[:low_abs].current}"
73
+ out_2 = if @stochastics_slow.current.present? # warmup perioda is over
74
+ @stochastics_slow.current
75
+ else
76
+ nil
77
+ end
78
+ end
79
+ @buffer << { fast: out_1, slow: out_2 }
80
+
81
+ current # return the last buffer value
82
+ end
83
+
84
+ # returns the ema-buffer
85
+ def stochastics
86
+ @buffer
87
+ end
88
+
89
+ alias lane stochastics
90
+
91
+ # returns the ema if the last computed item
92
+ def current
93
+ obj = @buffer.last
94
+ if obj.values.compact.empty?
95
+ nil
96
+ else
97
+ obj
98
+ end
99
+ end
100
+ end
101
+
102
+ # set alias for the class
103
+ LaneStochastic = Lane
104
+ end
105
+ end
@@ -0,0 +1,76 @@
1
+ module TechnicalAnalysis
2
+ module Momentum
3
+ RSI = Struct.new :time, :value
4
+
5
+ class Rsi
6
+ =begin
7
+
8
+
9
+ The RSI is calulated by
10
+
11
+ ExpMA( Deltas of up-trending Periods )
12
+ RSI = ----------------------------------------------
13
+ ExpMA( Deltas of down-trending Periods )
14
+
15
+
16
+ If the average of Delta values is zero, then according to the equation, the RS value will approach infinitiy
17
+ =end
18
+
19
+ def initialize period: 15, data: [], strict: true
20
+ # strict is always true
21
+
22
+ raise "Periods must be greater then one" if period <= 1
23
+
24
+ @queue = []
25
+ @buffer = []
26
+ @period= period
27
+
28
+ @emas = [ TechnicalAnalysis::MovingAverage::ExpMA.new( period: period, strict: true ),
29
+ TechnicalAnalysis::MovingAverage::ExpMA.new( period: period, strict: true ) ]
30
+
31
+ if !data.empty?
32
+ data.map{|d| add_item d }
33
+ end
34
+ end
35
+
36
+ # adds item, calculates the ema, puts value to the buffer and returns the result
37
+ def add_item value
38
+ @queue << value.to_f
39
+
40
+ up_or_down = -> (previous, actual) do
41
+ if actual >= previous
42
+ [actual-previous,0]
43
+ else
44
+ [0,previous-actual]
45
+ end
46
+ end
47
+
48
+ if @queue.size < 2
49
+ @buffer << nil
50
+ else
51
+ # up-trend-data are added to @emas.first, downtrend-data to @emas.last
52
+ # the lambda up_and_down always returns positve data (or "0")
53
+
54
+ @emas.zip( up_or_down[ *@queue[-2,2] ] ).each { | exp_ma, item | exp_ma.add_item item }
55
+
56
+ if @emas.first.current.present?
57
+ @buffer << 100 - ( 100 / (1 + ( @emas.first.current / @emas.last.current rescue 100 ) ) )
58
+ end
59
+
60
+ current # return the last buffer value
61
+ end
62
+ end
63
+
64
+ # returns the ema-buffer
65
+ def momentum
66
+ @buffer
67
+ end
68
+
69
+ # returns the ema if the last computed item
70
+ def current
71
+ @buffer.last
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,79 @@
1
+ module TechnicalAnalysis
2
+ module Momentum
3
+ TSI = Struct.new :time, :value
4
+
5
+ class Tsi
6
+ =begin
7
+ ExpMA(low) ( ExpMA(high ) prices )
8
+ TSI = ----------------------------------------------
9
+ ExpMA(low) ( ExpMA(high ) prices.abs )
10
+
11
+
12
+ While the TSI output is bound between +1 and −1, most values fall between +0.25 and −0.25.
13
+ Blau suggests interpreting these values as overbought and oversold levels, respectively,
14
+ at which point a trader may anticipate a market turn.
15
+
16
+ Trend direction is indicated by the slope of the TSI;
17
+ a rising TSI suggests an up-trend in the market, and a falling TSI suggests a down-trend
18
+
19
+ =end
20
+
21
+ def initialize low: 13, high: 25, data: [], strict: true
22
+ # strict is always true
23
+
24
+ raise "Periods must be greater then one" if low <= 1 || high <= 1
25
+ raise "high must be greater then low" if low > high
26
+
27
+ @queue = []
28
+ @buffer = []
29
+ @low= low
30
+ @high = high
31
+
32
+ @emas = { high: TechnicalAnalysis::MovingAverage::ExpMA.new( period: high, strict: true ),
33
+ low: TechnicalAnalysis::MovingAverage::ExpMA.new( period: low, strict: true ),
34
+ high_abs: TechnicalAnalysis::MovingAverage::ExpMA.new( period: high, strict: true ),
35
+ low_abs: TechnicalAnalysis::MovingAverage::ExpMA.new( period: low, strict: true ) }
36
+
37
+ if !data.empty?
38
+ data.map{|d| add_item d }
39
+ end
40
+ end
41
+
42
+ # adds item, calculates the ema, puts value to the buffer and returns the result
43
+ def add_item value
44
+ @queue << value.to_f
45
+
46
+ if @queue.size < 2
47
+ @buffer << nil
48
+ else
49
+ momentum = @queue.last - @queue[-2]
50
+ @emas[:high].add_item momentum
51
+ @emas[:high_abs].add_item momentum.abs
52
+
53
+ if @emas[:high].current.present?
54
+ @emas[:low].add_item @emas[:high].current
55
+ @emas[:low_abs].add_item @emas[:high_abs].current
56
+ end
57
+
58
+ #puts "momentum: #{@emas[:low].current} <--> #{@emas[:low_abs].current}"
59
+ if @emas[:low].current.present? # warmup perioda is over
60
+ @buffer << @emas[:low].current / @emas[:low_abs].current
61
+ end
62
+
63
+ current # return the last buffer value
64
+ end
65
+ end
66
+
67
+ # returns the ema-buffer
68
+ def momentum
69
+ @buffer
70
+ end
71
+
72
+ # returns the ema if the last computed item
73
+ def current
74
+ @buffer.last
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,59 @@
1
+ module TechnicalAnalysis
2
+ module MovingAverage
3
+
4
+
5
+
6
+ class Base
7
+ def initialize period: 15, data: [], strict: true
8
+
9
+ raise "Period must be greater then one" if period <= 1
10
+ @period = period
11
+ @strict= strict
12
+ # queue for relevant data to process the calculation
13
+ @queue = []
14
+ # buffer contains the calculated indicator values
15
+ @buffer = []
16
+ end
17
+
18
+ # adds item, calculates the ema, puts value to the buffer and returns the result
19
+ def add_item value
20
+ @queue << value
21
+ @queue.shift if @queue.size > @period
22
+ if @buffer.empty?
23
+ @buffer=[value.to_f]
24
+ else
25
+ prev_ema = @buffer.last
26
+ current_value = value.to_f
27
+ @buffer << (current_value - prev_ema) * @smooth_constant + prev_ema
28
+ end
29
+ current # return the last buffer value
30
+ end
31
+
32
+ # returns the moving-average-buffer
33
+ def ma
34
+ if @strict
35
+ @buffer.drop @period-1
36
+ else
37
+ @buffer
38
+ end
39
+ end
40
+
41
+
42
+ # returns the ema of the last computed item
43
+ def current
44
+ if @strict && warmup?
45
+ nil
46
+ else
47
+ @buffer.last
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def warmup?
54
+ @queue.size < @period
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,53 @@
1
+ module TechnicalAnalysis
2
+ module MovingAverage
3
+ EMA = Struct.new :time, :value
4
+
5
+
6
+ # Calculates the exponential moving average (EMA) for the data over the given period
7
+ # https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
8
+ #
9
+ # Takes a block which replaces the _smooth-constant_
10
+ #
11
+ # z = Symbols::Futures.mini_dax.eod( duration: '90 d')
12
+ # TechnicalAnalysis::MovingAverage::ExpMA.new( data=z.map(&:close))
13
+ #
14
+ # returns an array with the calculated moving-average data
15
+ #
16
+ # ema = TechnicalAnalysis::MovingAverage::ExpMA.new( default_value = z.first[:close])
17
+ # moving_average = z.map{|x| EMA.new x.time, ema.add_item(x.close)}
18
+ #
19
+ # returns an array of EMA-Objects
20
+ #
21
+
22
+ class ExpMA < Base
23
+ def initialize period: 15, data: [], strict: true
24
+ super
25
+
26
+ @smooth_constant = if block_given?
27
+ yield period
28
+ else
29
+ (2.0 / (period + 1.0))
30
+ end
31
+ if !data.empty?
32
+ data.map{|d| add_item d }
33
+ end
34
+ end
35
+
36
+ # adds item, calculates the ema, puts value to the buffer and returns the result
37
+ def add_item value
38
+ @queue << value
39
+ @queue.shift if @queue.size > @period
40
+ if @buffer.empty?
41
+ @buffer=[value.to_f]
42
+ else
43
+ prev_ema = @buffer.last
44
+ current_value = value.to_f
45
+ @buffer << (current_value - prev_ema) * @smooth_constant + prev_ema
46
+ end
47
+ current # return the last buffer value
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,90 @@
1
+ module TechnicalAnalysis
2
+ module MovingAverage
3
+ KAMA = Struct.new :time, :value
4
+
5
+
6
+ # Calculates the exponential moving average (EMA) for the data over the given period
7
+ # https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
8
+ #
9
+ # Takes a block which replaces the _smooth-constant_
10
+ #
11
+ # z = Symbols::Futures.mini_dax.eod( duration: '90 d')
12
+ # TechnicalAnalysis::MovingAverage::ExpMA.new( data=z.map(&:close))
13
+ #
14
+ # returns an array with the calculated moving-average data
15
+ #
16
+ # ema = TechnicalAnalysis::MovingAverage::ExpMA.new( default_value = z.first[:close])
17
+ # moving_average = z.map{|x| EMA.new x.time, ema.add_item(x.close)}
18
+ #
19
+ # returns an array of EMA-Objects
20
+ #
21
+
22
+ class KaMA
23
+ def initialize period: 10, fast: 2, slow: 30, data: []
24
+
25
+ raise "Period must be greater then one" if period <= 1
26
+
27
+ @smoothConst_fast = 2 / (fast +1).to_f
28
+ @smoothConst_slow = 2 / (slow +1).to_f
29
+
30
+ @period = period
31
+ @queue = []
32
+ @buffer = []
33
+
34
+ if !data.empty?
35
+ data.map{|d| add_item d }
36
+ end
37
+ end
38
+
39
+ # adds item, calculates the kama, puts value to the buffer and returns the result
40
+ def add_item value
41
+ begin
42
+ @queue << value.to_f
43
+ rescue NoMethodError => e
44
+ puts "add item only supports single values. Using the :close method"
45
+ @queue << value.send(:close) || value
46
+ end
47
+ if @queue.size < @period
48
+ @buffer = [ @queue.sum / @queue.size ] # fill buffer with average
49
+ else
50
+ @queue.shift if @queue.size > @period
51
+ @buffer << calculate
52
+ end
53
+ current # return the last buffer value
54
+ end
55
+
56
+ # returns the kama-buffer
57
+ def kaema
58
+ @buffer
59
+ end
60
+
61
+ # returns the kama of the last computed item
62
+ def current
63
+ @buffer.last
64
+ end
65
+
66
+ private
67
+ def calculate
68
+ # define the Effiency Ratio to be used by the "kaufmans Adaptive Moving Average" : kama
69
+ # ER is calculated by dividing the absolute difference between the
70
+ # current price and the price at the beginning of the period by the sum
71
+ # of the absolute difference between each pair of closes during the
72
+ # period.
73
+ # | x(t) - x(t-n) |
74
+ # er = ----------------------------
75
+ # sum | x(i) - x(i-1) |
76
+
77
+ er= (@queue.first - @queue.last).abs /
78
+ (1..@queue.size-1).map{|x| (@queue[x] - @queue[x-1]).abs }.sum # rescue 1
79
+
80
+ alpha = (er * ( @smoothConst_fast - @smoothConst_slow ) + @smoothConst_slow )**2
81
+ prev_kama = @buffer.last
82
+ current_value = @queue.last
83
+
84
+ @buffer.last + alpha * (current_value - prev_kama)
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+ end