Class: SQA::Indicator
- Inherits:
-
Object
- Object
- SQA::Indicator
- 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
- .average_true_range(high_prices, low_prices, close_prices, period) ⇒ Object (also: atr)
- .bollinger_bands(prices, period, num_std_devs = 2) ⇒ Object (also: bb)
- .candlestick_pattern_recognizer(open_prices, high_prices, low_prices, close_prices) ⇒ Object
- .donchian_channel(prices, period) ⇒ Object
- .double_top_bottom_pattern(prices) ⇒ Object
- .elliott_wave_theory(prices) ⇒ Object
- .exponential_moving_average(prices, period) ⇒ Object (also: ema)
- .exponential_moving_average_trend(prices, period) ⇒ Object (also: ema_trend)
- .fibonacci_retracement(swing_high, swing_low) ⇒ Object (also: fr)
- .head_and_shoulders_pattern?(prices) ⇒ Boolean
- .market_profile(volumes, prices, support_threshold, resistance_threshold) ⇒ Object (also: mp)
-
.mean_reversion?(prices, lookback_period, deviation_threshold) ⇒ Boolean
True if the stock exhibits mean reversion behavior, false otherwise.
- .momentum(prices, period) ⇒ Float (also: m)
- .moving_average_convergence_divergence(prices, short_period, long_period, signal_period) ⇒ Object (also: macd)
- .mr_mean(prices, lookback_period) ⇒ Object
- .peaks_and_valleys(prices, delta) ⇒ Object (also: pav)
- .pnv2(stock, window, testing = false) ⇒ Object
- .pnv3(stock, window, testing = false) ⇒ Object
- .pnv4(stock, window, testing = false) ⇒ Object
- .pnv5(stock, window, testing = false) ⇒ Object
- .predict_next_values(stock, window, testing = false) ⇒ Object (also: pnv)
-
.prediction_test(actual, forecast) ⇒ Object
Produce a Table show actual values and forecasted values.
- .relative_strength_index(prices, period, over_sold = 30.0, over_bought = 70.0) ⇒ Object (also: rsi)
- .simple_moving_average(prices, period) ⇒ Object (also: sma)
- .simple_moving_average_trend(prices, period, delta = 1.0) ⇒ Object (also: sma_trend)
-
.stochastic_oscillator(high_prices, low_prices, closing_prices, period, smoothing_period) ⇒ Array
(also: so)
An array of %K and %D values.
- .stochastic_oscillator2(prices, period) ⇒ Object (also: so2)
- .true_range(high_prices, low_prices, closing_prices) ⇒ Array (also: tr)
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
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.
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
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.
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
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 |