Class: SQA::Indicator

Inherits:
Object
  • Object
show all
Defined in:
lib/sqa/indicator.rb,
lib/sqa/indicator/momentum.rb,
lib/sqa/indicator/true_range.rb,
lib/sqa/indicator/market_profile.rb,
lib/sqa/indicator/mean_reversion.rb,
lib/sqa/indicator/bollinger_bands.rb,
lib/sqa/indicator/donchian_channel.rb,
lib/sqa/indicator/peaks_and_valleys.rb,
lib/sqa/indicator/average_true_range.rb,
lib/sqa/indicator/predict_next_value.rb,
lib/sqa/indicator/elliott_wave_theory.rb,
lib/sqa/indicator/fibonacci_retracement.rb,
lib/sqa/indicator/simple_moving_average.rb,
lib/sqa/indicator/stochastic_oscillator.rb,
lib/sqa/indicator/relative_strength_index.rb,
lib/sqa/indicator/double_top_bottom_pattern.rb,
lib/sqa/indicator/exponential_moving_average.rb,
lib/sqa/indicator/head_and_shoulders_pattern.rb,
lib/sqa/indicator/simple_moving_average_trend.rb,
lib/sqa/indicator/candlestick_pattern_recognizer.rb,
lib/sqa/indicator/exponential_moving_average_trend.rb,
lib/sqa/indicator/moving_average_convergence_divergence.rb

Overview

lib/sqa/indicator/moving_average_convergence_divergence.rb

Class Method Summary collapse

Class Method Details

.average_true_range(high_prices, low_prices, close_prices, period) ⇒ Object Also known as: atr



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/sqa/indicator/average_true_range.rb', line 7

def average_true_range(
      high_prices,  # Array of the day's high price
      low_prices,   # Array of the day's low price
      close_prices, # Array of the day's closing price
      period        # Integer the number of days to consider
    )
  true_ranges = true_range(high_prices, low_prices, close_prices)
  atr_values  = []

  window_span = period - 1

  true_ranges.size.times do |inx|
    start_inx = inx - window_span
    end_inx   = start_inx + window_span

    start_inx = 0 if start_inx < 0

    window    = true_ranges[start_inx..end_inx]

    atr_values << window.mean
  end

  atr_values # Array
end

.bollinger_bands(prices, period, num_std_devs = 2) ⇒ Object Also known as: bb



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/sqa/indicator/bollinger_bands.rb', line 5

def bollinger_bands(
      prices,         # Array of prices
      period,         # Integer number of entries to consider
      num_std_devs=2  # Integer number of standard deviations
    )
  moving_averages     = simple_moving_average(prices, period)
  standard_deviations = []

  prices.each_cons(period) do |window|
    standard_deviation = Math.sqrt(window.map { |price| (price - moving_averages.last) ** 2 }.sum / period)
    standard_deviations << standard_deviation
  end

  upper_band = moving_averages.last + (num_std_devs * standard_deviations.last)
  lower_band = moving_averages.last - (num_std_devs * standard_deviations.last)

  {
    upper_band: upper_band, # Array
    lower_band: lower_band  # Array
  }
end

.candlestick_pattern_recognizer(open_prices, high_prices, low_prices, close_prices) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/sqa/indicator/candlestick_pattern_recognizer.rb', line 5

def candlestick_pattern_recognizer(
      open_prices,  # Array day's ppen price
      high_prices,  # Array day's high price
      low_prices,   # Array day's low price
      close_prices  # Array day's closing price
    )
  patterns = []

  close_prices.each_with_index do |close, index|
    if index >= 2
      previous_close  = close_prices[index  - 1]
      previous_open   = open_prices[index   - 1]
      previous_high   = high_prices[index   - 1]
      previous_low    = low_prices[index    - 1]

      second_previous_close = close_prices[index  - 2]
      second_previous_open  = open_prices[index   - 2]
      second_previous_high  = high_prices[index   - 2]
      second_previous_low   = low_prices[index    - 2]

      if  close           > previous_close  &&
          previous_close  < previous_open   &&
          close           < previous_open   &&
          close           > previous_low    &&
          close           > second_previous_close
        patterns << :bullish_engulfing

      elsif close           < previous_close  &&
            previous_close  > previous_open   &&
            close           > previous_open   &&
            close           < previous_high   &&
            close           < second_previous_close
        patterns << :bearish_engulfing

      elsif close           > previous_close  &&
            previous_close  < previous_open   &&
            close           < previous_open   &&
            close           < previous_low    &&
            close           < second_previous_close
        patterns << :bearish_harami

      elsif close           < previous_close  &&
            previous_close  > previous_open   &&
            close           > previous_open   &&
            close           > previous_high   &&
            close           > second_previous_close
        patterns << :bullish_harami
      end
    end
  end

  patterns
end

.donchian_channel(prices, period) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/sqa/indicator/donchian_channel.rb', line 5

def donchian_channel(
      prices,   # Array of prices
      period    # Integer number of entries to consider
    )
  max               = -999999999
  min               = 999999999
  donchian_channel  = []

  prices.each_with_index do |value, index|
    value = value.to_f
    max   = value if value > max
    min   = value if value < min

    if index >= period - 1
      donchian_channel << [max, min, (max + min) / 2]
      max = -999999999
      min = 999999999
    end
  end

  donchian_channel
end

.double_top_bottom_pattern(prices) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/sqa/indicator/double_top_bottom_pattern.rb', line 5

def double_top_bottom_pattern(
      prices  # Array of prices
    )
  return :no_pattern if prices.length < 5

  data                = prices.last(5)

  first_peak          = data[0]
  valley              = data[1]
  second_peak         = data[2]
  neckline            = data[3]
  confirmation_price  = data[4]

  if  first_peak          < second_peak &&
      valley              > first_peak  &&
      valley              > second_peak &&
      confirmation_price  < neckline
    :double_top
  elsif first_peak          > second_peak &&
        valley              < first_peak  &&
        valley              < second_peak &&
        confirmation_price  > neckline
    :double_bottom
  else
    :no_pattern
  end
end

.elliott_wave_theory(prices) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/sqa/indicator/elliott_wave_theory.rb', line 15

def elliott_wave_theory(
      prices       # Array of prices
    )
  waves       = []
  wave_start  = 0

  (1..prices.length-2).each do |x|
    turning_point = prices[x] > prices[x-1] && prices[x] > prices[x+1] ||
                    prices[x] < prices[x-1] && prices[x] < prices[x+1]

    if turning_point
      waves << prices[wave_start..x]
      wave_start = x + 1
    end
  end

  analysis = []

  waves.each do |wave|
    analysis << {
      wave:     wave,
      pattern:  ewt_identify_pattern(wave)
    }
  end

  analysis
end

.exponential_moving_average(prices, period) ⇒ Object Also known as: ema



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/sqa/indicator/exponential_moving_average.rb', line 5

def exponential_moving_average(
      prices, # Array of prices
      period  # Integer number of entries to consider
    )

  ema_values = []
  ema_values << prices.first

  multiplier = (2.0 / (period + 1))

  (1...prices.length).each do |x|
    ema = (prices[x] - ema_values.last) * multiplier + ema_values.last
    ema_values << ema
  end

  ema_values
end

.exponential_moving_average_trend(prices, period) ⇒ Object Also known as: ema_trend



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/sqa/indicator/exponential_moving_average_trend.rb', line 5

def exponential_moving_average_trend(
      prices, # Array of prices
      period  # Integer number of entries to consider
    )

  ema_values  = exponential_moving_average(
                  prices,
                  period
                )

  last_ema      = ema_values.last
  previous_ema  = ema_values[-2]

  trend = if last_ema > previous_ema
            :up
          elsif last_ema < previous_ema
            :down
          else
            :neutral
          end

  {
    ema:        ema_values,
    trend:      trend,
    support:    ema_values.min,
    resistance: ema_values.max
  }
end

.fibonacci_retracement(swing_high, swing_low) ⇒ Object Also known as: fr



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/sqa/indicator/fibonacci_retracement.rb', line 5

def fibonacci_retracement(
      swing_high,   # Float peak price in a period - peak
      swing_low     # Float bottom price in a period - valley
    )
  retracement_levels = []

  fibonacci_levels = [0.236, 0.382, 0.5, 0.618, 0.786]

  fibonacci_levels.each do |level|
    retracement_levels << swing_low + (swing_high - swing_low) * level
  end


  retracement_levels # Array
end

.head_and_shoulders_pattern?(prices) ⇒ Boolean

Returns:

  • (Boolean)


6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/sqa/indicator/head_and_shoulders_pattern.rb', line 6

def head_and_shoulders_pattern?(
      prices  # Array of prices
    )

  return false if prices.length < 5

  data            = prices.last(5)

  left_shoulder   = data[0]
  head            = data[1]
  right_shoulder  = data[2]
  neckline        = data[3]
  right_peak      = data[4]

  head        > left_shoulder   &&
  head        > right_shoulder  &&
  right_peak  < neckline
end

.market_profile(volumes, prices, support_threshold, resistance_threshold) ⇒ Object Also known as: mp



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/sqa/indicator/market_profile.rb', line 5

def market_profile(
      volumes,              # Array of volumes
      prices,               # Array of prices
      support_threshold,    # Float stock's support price estimate
      resistance_threshold  # Float stock's resistance price estimate
    )
  total_volume    = volumes.sum
  average_volume  = volumes.mean
  max_volume      = volumes.max

  support_levels    = prices.select { |price| price <= support_threshold }
  resistance_levels = prices.select { |price| price >= resistance_threshold }

  if  support_levels.empty?     &&
      resistance_levels.empty?
    :neutral
  elsif support_levels.empty?
    :resistance
  elsif resistance_levels.empty?
    :support
  else
    :mixed
  end
end

.mean_reversion?(prices, lookback_period, deviation_threshold) ⇒ Boolean

Returns True if the stock exhibits mean reversion behavior, false otherwise.

Parameters:

  • prices (Array)
  • lookback_period (Integer)
  • deviation_threshold (Float)

Returns:

  • (Boolean)

    True if the stock exhibits mean reversion behavior, false otherwise.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/sqa/indicator/mean_reversion.rb', line 12

def mean_reversion?(
      prices,             # Array of prices
      lookback_period,    # Integer number of events to consider
      deviation_threshold # Float delta change at which a price is considered to be over extended
    )

  return false if prices.length < lookback_period

  mean      = mr_mean(prices, lookback_period)
  deviation = prices[-1] - mean

  if deviation.abs > deviation_threshold
    return true
  else
    return false
  end
end

.momentum(prices, period) ⇒ Float Also known as: m

Parameters:

  • prices (Array)
  • period (Integer)

Returns:

  • (Float)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/sqa/indicator/momentum.rb', line 10

def momentum(
      prices, # Array of prices
      period  # Integer number of entries to consider
    )

  momentums = []

  prices.each_cons(period) do |window|
    current_price = window.last.to_f
    past_price    = window.first.to_f
    momentums    << 10.0 * ( (current_price - past_price) / past_price)
  end

  momentums  # Array
end

.moving_average_convergence_divergence(prices, short_period, long_period, signal_period) ⇒ Object Also known as: macd



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/sqa/indicator/moving_average_convergence_divergence.rb', line 5

def moving_average_convergence_divergence(
      prices,
      short_period,
      long_period,
      signal_period
    )

  short_ma    = simple_moving_average(prices, short_period)
  long_ma     = simple_moving_average(prices, long_period)
  signal_line = simple_moving_average(short_ma, signal_period)
  macd_line   = []

  prices.size.times do |x|
    macd_line << short_ma[x] - long_ma[x]
  end

  {
    macd:   macd_line,  # Array
    signal: signal_line # Array
  }
end

.mr_mean(prices, lookback_period) ⇒ Object



31
32
33
# File 'lib/sqa/indicator/mean_reversion.rb', line 31

def mr_mean(prices, lookback_period)
  prices.last(lookback_period).sum / lookback_period.to_f
end

.peaks_and_valleys(prices, delta) ⇒ Object Also known as: pav



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/sqa/indicator/peaks_and_valleys.rb', line 4

def peaks_and_valleys(
      prices,   # Array of prices
      delta     # Integer distance delta (# of higher or lower prices to either side)
    )
  peaks   = []
  valleys = []
  period  = 2 * delta + 1

  prices.each_cons(period) do |window|
    price    = window[delta]

    next if window.count(price) == period

    peaks   << price if window.max == price
    valleys << price if window.min == price
  end

  {
    period:   period,
    peaks:    peaks,
    valleys:  valleys
  }
end

.pnv2(stock, window, testing = false) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/sqa/indicator/predict_next_value.rb', line 107

def pnv2(stock, window, testing=false)
  prices  = stock.df.adj_close_price.to_a
  known   = prices.pop(window) if testing

  result    = []
  last_inx  = prices.size - 1 # indexes are zero based

  window.times do |x|
    x += 1 # forecasting 1 day into the future needs 2 days of near past data

    # window is the near past values
    window  = prices[last_inx-x..]

    high      = window.max
    low       = window.min
    midpoint  = (high + low) / 2.0

    result   << [high, midpoint, low]
  end

  prediction_test(known, result) if testing

  result
end

.pnv3(stock, window, testing = false) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/sqa/indicator/predict_next_value.rb', line 133

def pnv3(stock, window, testing=false)
  prices  = stock.df.adj_close_price.to_a
  known   = prices.pop(window) if testing

  result  = []
  known   = prices.last(window)

  last_inx = prices.size - 1

  (0..window-1).to_a.reverse.each do |x|
    curr_inx          = last_inx - x
    prev_inx          = curr_inx - 1
    current_price     = prices[curr_inx]
    percentage_change = (current_price - prices[prev_inx]) / prices[prev_inx]

    result << current_price + (current_price * percentage_change)
  end

  prediction_test(known, result) if testing

  result
end

.pnv4(stock, window, testing = false) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/sqa/indicator/predict_next_value.rb', line 157

def pnv4(stock, window, testing=false)
  prices  = stock.df.adj_close_price.to_a
  known   = prices.pop(window) if testing

  result        = []
  known         = prices.last(window).dup
  current_price = known.last

  # Loop through the prediction window size
  (1..window).each do |x|

    # Calculate the percentage change between the current price and its previous price
    percentage_change = (current_price - prices[-x]) / prices[-x]

    result << current_price + (current_price * percentage_change)
  end

  prediction_test(known, result) if testing

  result
end

.pnv5(stock, window, testing = false) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/sqa/indicator/predict_next_value.rb', line 180

def pnv5(stock, window, testing=false)
  prices  = stock.df.adj_close_price.to_a
  known   = prices.pop(window) if testing

  result            = []
  current_price     = prices.last

  rate              = 0.9 # convert angle into percentage
  sma_trend         = stock.indicators.sma_trend
  percentage_change = 1 + (sma_trend[:angle] / 100.0) * rate

  # Assumes the SMA trend will continue
  window.times do |_|
    result       << current_price * percentage_change
    current_price = result.last
  end

  prediction_test(known, result) if testing

  result
end

.predict_next_values(stock, window, testing = false) ⇒ Object Also known as: pnv



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/sqa/indicator/predict_next_value.rb', line 82

def predict_next_values(stock, window, testing=false)
  prices  = stock.df.adj_close_price.to_a
  known   = prices.pop(window) if testing
  result  = []

  prices.each_cons(2) do |a, b|
    result << b + (b - a)
  end

  if window > 0
    (1..window).each do |_|
      last_two_values = result.last(2)
      delta           = last_two_values.last - last_two_values.first
      next_value      = last_two_values.last + delta
      result         << next_value
    end
  end

  prediction_test(known, result.last(window)) if testing

  result.last(window)
end

.prediction_test(actual, forecast) ⇒ Object

Produce a Table show actual values and forecasted values

actual .… Array of Float forecast .. Array of Float or Array of Array of Float

entry is either a single value or
an Array [high, guess, low]


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/sqa/indicator/predict_next_value.rb', line 15

def prediction_test(actual, forecast)

  unless actual.size == forecast.size
    debug_me("== ERROR =="){[
      "actual.size",
      "forecast.size"
    ]}
  end

  # Method Under Test (MUT)
  mut     = caller[0][/`([^']*)'/, 1]
  window  = actual.size
  hgl     = forecast.first.is_a?(Array)

  if hgl
    headers = %w[ Actual Forecast Diff %off InRange? High Low ]
  else
    headers = %w[ Actual Forecast Diff %off ]
  end

  diff    = []
  percent = []
  values  = []

  actual.map!{|v| v.round(3)}

  if hgl
    high  = forecast.map{|v| v[0].round(3)}
    guess = forecast.map{|v| v[1].round(3)}
    low   = forecast.map{|v| v[2].round(3)}
  else
    guess = forecast.map{|v| v.round(3)}
  end

  window.times do |x|
    diff    << (actual[x] - guess[x]).round(3)
    percent << ((diff.last / guess[x])*100.0).round(3)

    entry = [
      actual[x], guess[x],
      diff[x], percent[x],
    ]

    if hgl
      entry << ( (high[x] >= actual[x] && actual[x] >= low[x]) ? "Yes" : "" )
      entry << high[x]
      entry << low[x]
    end

    values << entry
  end

  the_table = TTY::Table.new(headers, values)

  puts "\n#{mut} Result Validation"

  puts  the_table.render(
          :unicode,
          {
            padding:    [0, 0, 0, 0],
            alignments:  [:right]*values.first.size,
          }
        )
  puts
end

.relative_strength_index(prices, period, over_sold = 30.0, over_bought = 70.0) ⇒ Object Also known as: rsi



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/sqa/indicator/relative_strength_index.rb', line 5

def relative_strength_index(
      prices,             # Array of prices
      period,             # Integer how many to consider at a time
      over_sold   = 30.0, # Float break over point in trend
      over_bought = 70.0  # Float break over point in trend
    )
  gains   = []
  losses  = []

  prices.each_cons(2) do |pair|
    change = pair[1] - pair[0]

    if change > 0
      gains   << change
      losses  << 0
    else
      gains   << 0
      losses  << change.abs
    end
  end

  avg_gain  = gains.last(period).sum  / period.to_f
  avg_loss  = losses.last(period).sum / period.to_f
  rs        = avg_gain / avg_loss
  rsi       = 100 - (100 / (1 + rs))

  trend = if rsi >= over_bought
            :over_bought
          elsif rsi <= over_sold
            :over_sold
          else
            :normal
          end

  {
    rsi:    rsi,  # Float
    trend:  trend # Symbol :normal, :over_bought, :over+sold
  }
end

.simple_moving_average(prices, period) ⇒ Object Also known as: sma



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/sqa/indicator/simple_moving_average.rb', line 5

def simple_moving_average(
      prices, # Array of prices
      period  # Integer how many to consider at a time
    )
  moving_averages = []

  (0..period-2).to_a.each do |x|
    moving_averages << prices[0..x].mean
  end

  prices.each_cons(period) do |window|
    moving_averages << window.mean
  end

  moving_averages # Array
end

.simple_moving_average_trend(prices, period, delta = 1.0) ⇒ Object Also known as: sma_trend



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/sqa/indicator/simple_moving_average_trend.rb', line 5

def simple_moving_average_trend(
      prices,           # Array of prices
      period,           # Integer number of entries to consider
      delta = 1.0       # Float defines the angle range(+/-) for :neutral trend
    )
  sma       = simple_moving_average(prices, period)
  last_sma  = sma.last
  prev_sma  = sma.last(period).first
  angle     = Math.atan((last_sma - prev_sma) / period) * (180 / Math::PI)

  trend = if angle > delta
            :up
          elsif angle < -delta
            :down
          else
            :neutral
          end

  {
    sma:    sma,    # Array
    trend:  trend,  # Symbol :up, :down, :neutral
    angle:  angle   # Float how step the trend
  }
end

.stochastic_oscillator(high_prices, low_prices, closing_prices, period, smoothing_period) ⇒ Array Also known as: so

Returns An array of %K and %D values.

Parameters:

  • high_prices (Array)
  • low_prices (Array)
  • close_prices (Array)
  • period (Integer)
  • smoothing_period (Integer)

Returns:

  • (Array)

    An array of %K and %D values.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/sqa/indicator/stochastic_oscillator.rb', line 13

def stochastic_oscillator(
      high_prices,      # Array of high prices
      low_prices,       # Array of low prices
      closing_prices,   # Array of closing prices
      period,           # Integer The period for calculating the Stochastic Oscillator
      smoothing_period  # Integer The smoothing period for %K line
    )
  k_values = []
  d_values = []

  closing_prices.each_cons(period) do |window|
    highest_high  = high_prices.max(period)
    lowest_low    = low_prices.min(period)
    current_close = window.last
    k_values     << (current_close - lowest_low) / (highest_high - lowest_low) * 100 # Calculate the k_value
  end

  k_values.each_cons(smoothing_period) do |k_values_subset|
    d_values << k_values_subset.sum / smoothing_period.to_f # Calculate the d_value
  end

  [k_values, d_values]
end

.stochastic_oscillator2(prices, period) ⇒ Object Also known as: so2



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/sqa/indicator/stochastic_oscillator.rb', line 39

def stochastic_oscillator2(
      prices, # Array of prices
      period  # Integer number of events to consider
    )
  k_values = []
  d_values = []

  prices.each_cons(period) do |window|
    low           = window.min  # Lowest  price in the period
    high          = window.max  # Highest price in the period
    current_price = window.last # Current closing price

    k_values << (current_price - low) * 100 / (high - low)
  end

  k_values.each_cons(period) do |window|
    d_values << window.mean
  end

  {
    k: k_values,
    d: d_values
  }
end

.true_range(high_prices, low_prices, closing_prices) ⇒ Array Also known as: tr

Parameters:

  • high_prices (Array)
  • low_prices (Array)
  • previous_closes (Array)

Returns:

  • (Array)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/sqa/indicator/true_range.rb', line 13

def true_range(
      high_prices,    # Array of high prices
      low_prices,     # Array of low prices
      closing_prices  # Array of closing prices
    )
  true_ranges = []

  high_prices.each_with_index do |high, index|
    if index > 0
      low             = low_prices[index]
      previous_close  = closing_prices[index - 1]

      true_range = [
        high  - low,
        (high - previous_close).abs,
        (low  - previous_close).abs
      ].max

      true_ranges << true_range
    end
  end

  true_ranges # Array of True Range values
end