ib-technical-analysis 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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