From 47fa2d82c78d59284c53ad7e250c32a843aa9a32 Mon Sep 17 00:00:00 2001 From: John Feustel Date: Sat, 5 Jan 2019 10:04:57 -0700 Subject: [PATCH 1/3] Start of ADI indicator --- lib/technical_analysis.rb | 3 +- lib/technical_analysis/indicators/adi.rb | 34 ++++++++++++ .../technical_analysis/indicators/adi_spec.rb | 52 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 lib/technical_analysis/indicators/adi.rb create mode 100644 spec/technical_analysis/indicators/adi_spec.rb diff --git a/lib/technical_analysis.rb b/lib/technical_analysis.rb index fed0241..9bb42da 100644 --- a/lib/technical_analysis.rb +++ b/lib/technical_analysis.rb @@ -5,4 +5,5 @@ require 'technical_analysis/helpers/validation' # Indicators -require 'technical_analysis/indicators/sma' \ No newline at end of file +require 'technical_analysis/indicators/sma' +require 'technical_analysis/indicators/adi' \ No newline at end of file diff --git a/lib/technical_analysis/indicators/adi.rb b/lib/technical_analysis/indicators/adi.rb new file mode 100644 index 0000000..6aa9d31 --- /dev/null +++ b/lib/technical_analysis/indicators/adi.rb @@ -0,0 +1,34 @@ +module TechnicalAnalysis + class Adi + # Calculates the Accumulation/Distribution Index for the data over the given period + # https://en.wikipedia.org/wiki/Accumulation/distribution_index + # + # @param data [Hash] Date strings to hash with keys (:high, :low, :close, :volume) + # @return [Hash] A hash of date strings to ADI values + def self.calculate(data) + # Validation.validate_price_data(data) + # Validation.validate_length(data, period) + + ads = {} + data = data.sort.to_h # Sort data by descending dates + + data.each do |date, values| + clv = 0 + if values[:high] > values[:low] + clv = ((values[:close] - values[:low]) - (values[:high] - values[:close])) / (values[:high] - values[:low]) + end + ad = clv * values[:volume] + ads[date] = ad + end + + keys = ads.keys + (1..keys.size-1).each do |index| + cur_key = keys[index] + prev_key = keys[index-1] + ads[cur_key] = ads[cur_key] + ads[prev_key] + end + + ads + end + end +end diff --git a/spec/technical_analysis/indicators/adi_spec.rb b/spec/technical_analysis/indicators/adi_spec.rb new file mode 100644 index 0000000..6ef9179 --- /dev/null +++ b/spec/technical_analysis/indicators/adi_spec.rb @@ -0,0 +1,52 @@ +require 'technical-analysis' + +describe 'Indicators' do + describe "ADI" do + input_data = { + "2018-12-31": {open: 157.74, high: 158.74, low: 154.74, close: 158.14, volume: 1000}, + "2018-12-28": {open: 156.23, high: 156.23, low: 156.23, close: 156.23, volume: 1000}, + "2018-12-27": {open: 156.15, high: 156.15, low: 156.15, close: 156.15, volume: 1000}, + "2018-12-26": {open: 157.17, high: 157.17, low: 157.17, close: 157.17, volume: 1000}, + "2018-12-24": {open: 146.83, high: 146.83, low: 146.83, close: 146.83, volume: 1000}, + "2018-12-21": {open: 150.73, high: 150.73, low: 150.73, close: 150.73, volume: 1000}, + "2018-12-20": {open: 156.83, high: 156.83, low: 156.83, close: 156.83, volume: 1000}, + "2018-12-19": {open: 160.89, high: 160.89, low: 160.89, close: 160.89, volume: 1000}, + "2018-12-18": {open: 166.07, high: 166.07, low: 166.07, close: 166.07, volume: 1000}, + "2018-12-17": {open: 163.94, high: 163.94, low: 163.94, close: 163.94, volume: 1000}, + "2018-12-14": {open: 165.48, high: 165.48, low: 165.48, close: 165.48, volume: 1000}, + "2018-12-13": {open: 170.95, high: 170.95, low: 170.95, close: 170.95, volume: 1000}, + "2018-12-12": {open: 169.10, high: 169.10, low: 169.10, close: 169.10, volume: 1000}, + "2018-12-11": {open: 168.63, high: 168.63, low: 168.63, close: 168.63, volume: 1000}, + "2018-12-10": {open: 169.60, high: 169.60, low: 169.60, close: 169.60, volume: 1000} + } + + describe 'Accumulation/Distribution Index' do + it 'Calculates ADI' do + output = TechnicalAnalysis::Adi.calculate(input_data) + + puts output + + expected_output = { + :"2018-12-14"=>168.752, + :"2018-12-17"=>167.61999999999998, + :"2018-12-18"=>167.108, + :"2018-12-19"=>165.46599999999998, + :"2018-12-20"=>162.642, + :"2018-12-21"=>159.692, + :"2018-12-24"=>156.27, + :"2018-12-26"=>154.49, + :"2018-12-27"=>153.54199999999997, + :"2018-12-28"=>153.422, + :"2018-12-31"=>154.823 + } + + expect(output).to eq(expected_output) + end + + it "Throws exception if not enough data" do + calc = Calculate.new + expect {TechnicalAnalysis::Sma.calculate(input_data, period: 30)}.to raise_exception(Validation::ValidationError) + end + end + end +end From f16b2cb63ef732f1c9581d794a91fd365318e956 Mon Sep 17 00:00:00 2001 From: John Feustel Date: Thu, 10 Jan 2019 16:14:18 -0700 Subject: [PATCH 2/3] Updated SMA and ADI to use new input/output data format (array of hashes). Spec helpers to use CSV sample data. Updated SMA and ADI specs to use new sample data. Data sort helpers. Validation updates. --- lib/technical_analysis/helpers/array.rb | 8 ++ lib/technical_analysis/helpers/validation.rb | 8 +- lib/technical_analysis/indicators/adi.rb | 35 +++--- lib/technical_analysis/indicators/sma.rb | 21 ++-- spec/spec_helper.rb | 26 +++++ spec/ta_test_data.csv | 64 +++++++++++ .../technical_analysis/indicators/adi_spec.rb | 105 ++++++++++++------ .../technical_analysis/indicators/sma_spec.rb | 98 ++++++++++------ 8 files changed, 264 insertions(+), 101 deletions(-) create mode 100644 spec/spec_helper.rb create mode 100644 spec/ta_test_data.csv diff --git a/lib/technical_analysis/helpers/array.rb b/lib/technical_analysis/helpers/array.rb index 8d60578..bc6e04c 100644 --- a/lib/technical_analysis/helpers/array.rb +++ b/lib/technical_analysis/helpers/array.rb @@ -2,4 +2,12 @@ class Array def sum self.inject(0, :+) end + + def sort_by_hash_date_asc + self.sort_by {|row| row[:date]} + end + + def sort_by_hash_date_desc + sort_by_hash_date_asc.reverse + end end \ No newline at end of file diff --git a/lib/technical_analysis/helpers/validation.rb b/lib/technical_analysis/helpers/validation.rb index 339fe2b..d122f5c 100644 --- a/lib/technical_analysis/helpers/validation.rb +++ b/lib/technical_analysis/helpers/validation.rb @@ -1,8 +1,10 @@ class Validation - def self.validate_price_data(data) - unless data.values.all? { |v| v.class == Float || v.class == Integer} - raise ValidationError.new "Invalid Data. Price is not a number" + def self.validate_numeric_data(data, *keys) + keys.each do |key| + unless data.all? { |v| v[key].is_a? Numeric} + raise ValidationError.new "Invalid Data. '#{key}' is not valid price data." + end end end diff --git a/lib/technical_analysis/indicators/adi.rb b/lib/technical_analysis/indicators/adi.rb index 6aa9d31..c5a00d9 100644 --- a/lib/technical_analysis/indicators/adi.rb +++ b/lib/technical_analysis/indicators/adi.rb @@ -1,33 +1,32 @@ module TechnicalAnalysis class Adi - # Calculates the Accumulation/Distribution Index for the data over the given period + # Calculates the Accumulation/Distribution Index for the given data # https://en.wikipedia.org/wiki/Accumulation/distribution_index # - # @param data [Hash] Date strings to hash with keys (:high, :low, :close, :volume) + # @param data [Array] Array of hashes with keys (:date, :high, :low, :close, :volume) # @return [Hash] A hash of date strings to ADI values def self.calculate(data) - # Validation.validate_price_data(data) - # Validation.validate_length(data, period) + Validation.validate_numeric_data(data, :high, :low, :close, :volume) - ads = {} - data = data.sort.to_h # Sort data by descending dates + data = data.sort_by_hash_date_asc + + ads = [] - data.each do |date, values| - clv = 0 - if values[:high] > values[:low] + clv = 0 + ad = 0 + prev_ad = 0 + data.each_with_index do |values, i| + if values[:high] == values[:low] + clv = 0 + else clv = ((values[:close] - values[:low]) - (values[:high] - values[:close])) / (values[:high] - values[:low]) end - ad = clv * values[:volume] - ads[date] = ad - end - keys = ads.keys - (1..keys.size-1).each do |index| - cur_key = keys[index] - prev_key = keys[index-1] - ads[cur_key] = ads[cur_key] + ads[prev_key] - end + ad = prev_ad + (clv * values[:volume]) + prev_ad = ad + ads << {date: values[:date], value: ad} + end ads end end diff --git a/lib/technical_analysis/indicators/sma.rb b/lib/technical_analysis/indicators/sma.rb index 813ee96..dce56af 100644 --- a/lib/technical_analysis/indicators/sma.rb +++ b/lib/technical_analysis/indicators/sma.rb @@ -3,21 +3,22 @@ class Sma # Calculates the simple moving average for the data over the given period # https://en.wikipedia.org/wiki/Moving_average#Simple_moving_average # - # @param data [Hash] Date strings to price values + # @param data [Array] Array of hashes with keys (:date, :value) # @param period [Integer] The given period to calculate the SMA - # @return [Hash] A hash of date strings to SMA values - def self.calculate(data, period: 30) - Validation.validate_price_data(data) + # @param price_key [Symbol] The hash key for the price data. Default :value + # @return [Hash] A hash with keys :date and :value + def self.calculate(data, period: 30, price_key: :value) + Validation.validate_numeric_data(data, price_key) Validation.validate_length(data, period) - output = {} - period_values = [] - data = data.sort.to_h # Sort data by descending dates + data = data.sort_by_hash_date_asc # Sort data by descending dates - data.each do |date, price| - period_values << price + output = [] + period_values = [] + data.each do |v| + period_values << v[price_key] if period_values.size == period - output[date] = period_values.sum / period.to_f + output << {date: v[:date], value: period_values.sum / period.to_f} period_values.shift end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..34f4bb5 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,26 @@ +require 'csv' + +class SpecHelper + TEST_DATA_PATH = File.join(File.dirname(__FILE__),'ta_test_data.csv') + FLOAT_KEYS = [:open, :high, :low, :close] + INTEGER_KEYS = [:volume] + + def self.get_test_data(*columns) + @data = CSV.read(TEST_DATA_PATH, headers: true) + columns = columns.map(&:to_sym) + output = [] + @data.each do |v| + col_hash = {date: v["date"]} + columns.each do |col| + value = v[col.to_s] + value = value.to_f if FLOAT_KEYS.include?(col) + value = value.to_i if INTEGER_KEYS.include?(col) + col_hash[col] = value + end + output << col_hash + end + output + end +end + + diff --git a/spec/ta_test_data.csv b/spec/ta_test_data.csv new file mode 100644 index 0000000..60f8763 --- /dev/null +++ b/spec/ta_test_data.csv @@ -0,0 +1,64 @@ +date,close,volume,open,high,low +"2019/01/09",153.3100,45034370.0000,151.2900,154.5300,149.6300 +"2019/01/08",150.7500,40622910.0000,149.5600,151.8200,148.5200 +"2019/01/07",147.9300,54571440.0000,148.7000,148.8300,145.9000 +"2019/01/04",148.2600,57423650.0000,144.5300,148.5499,143.8000 +"2019/01/03",142.1900,91106840.0000,143.9800,145.7200,142.0000 +"2019/01/02",157.9200,35637070.0000,154.8900,158.8500,154.2300 +"2018/12/31",157.7400,34499390.0000,158.5300,159.3600,156.4800 +"2018/12/28",156.2300,41740600.0000,157.5000,158.5200,154.5500 +"2018/12/27",156.1500,51608850.0000,155.8400,156.7700,150.0700 +"2018/12/26",157.1700,58133850.0000,148.3000,157.2300,146.7200 +"2018/12/24",146.8300,37169230.0000,148.1500,151.5500,146.5900 +"2018/12/21",150.7300,95497900.0000,156.8600,158.1600,149.6300 +"2018/12/20",156.8300,64398230.0000,160.4000,162.1100,155.3000 +"2018/12/19",160.8900,47597670.0000,166.0000,167.4500,159.0900 +"2018/12/18",166.0700,33753490.0000,165.3800,167.5300,164.3900 +"2018/12/17",163.9400,43250420.0000,165.4500,168.3500,162.7300 +"2018/12/14",165.4800,40620360.0000,169.0000,169.0800,165.2800 +"2018/12/13",170.9500,31754210.0000,170.4900,172.5700,169.5500 +"2018/12/12",169.1000,35474680.0000,170.4000,171.9200,169.0200 +"2018/12/11",168.6300,45968040.0000,171.6600,171.7900,167.0000 +"2018/12/10",169.6000,61759000.0000,165.0000,170.0900,163.3300 +"2018/12/07",168.4900,41678680.0000,173.4900,174.4900,168.3000 +"2018/12/06",174.7200,42704910.0000,171.7600,174.7800,170.4200 +"2018/12/04",176.6900,41141250.0000,180.9500,182.3899,176.2700 +"2018/12/03",184.8200,40537700.0000,184.4600,184.9400,181.2100 +"2018/11/30",178.5800,39424260.0000,180.2900,180.3300,177.0300 +"2018/11/29",179.5500,41523580.0000,182.6600,182.8000,177.7000 +"2018/11/28",180.9400,45941750.0000,176.7300,181.2900,174.9300 +"2018/11/27",174.2400,41156140.0000,171.5100,174.7700,170.8800 +"2018/11/26",174.6200,44662320.0000,174.2400,174.9500,170.2600 +"2018/11/23",172.2900,23623970.0000,174.9400,176.5950,172.1000 +"2018/11/21",176.7800,31096240.0000,179.7300,180.2700,176.5500 +"2018/11/20",176.9800,67678680.0000,178.3700,181.4700,175.5100 +"2018/11/19",185.8600,41626820.0000,190.0000,190.7000,184.9900 +"2018/11/16",193.5300,36191330.0000,190.5000,194.9695,189.4600 +"2018/11/15",191.4100,46271660.0000,188.3900,191.9700,186.9000 +"2018/11/14",186.8000,60547340.0000,193.9000,194.4800,185.9300 +"2018/11/13",192.2300,46725710.0000,191.6300,197.1800,191.4501 +"2018/11/12",194.1700,50991030.0000,199.0000,199.8500,193.7900 +"2018/11/09",204.4700,34317760.0000,205.5500,206.0100,202.2500 +"2018/11/08",208.4900,25289270.0000,209.9800,210.1200,206.7500 +"2018/11/07",209.9500,33291640.0000,205.9700,210.0600,204.1300 +"2018/11/06",203.7700,31774720.0000,201.9200,204.7200,201.6900 +"2018/11/05",201.5900,66072170.0000,204.3000,204.3900,198.1700 +"2018/11/02",207.4800,91046560.0000,209.5500,213.6500,205.4300 +"2018/11/01",222.2200,52954070.0000,219.0500,222.3600,216.8100 +"2018/10/31",218.8600,38016810.0000,216.8800,220.4500,216.6200 +"2018/10/30",213.3000,36487930.0000,211.1500,215.1800,209.2700 +"2018/10/29",212.2400,45713690.0000,219.1900,219.6900,206.0900 +"2018/10/26",216.3000,47191700.0000,215.9000,220.1900,212.6700 +"2018/10/25",219.8000,29027340.0000,217.7100,221.3800,216.7500 +"2018/10/24",215.0900,39992120.0000,222.6000,224.2300,214.5400 +"2018/10/23",222.7300,38681170.0000,215.8300,223.2500,214.7000 +"2018/10/22",220.6500,28751540.0000,219.7900,223.3600,218.9400 +"2018/10/19",219.3100,32874330.0000,218.0600,221.2600,217.4300 +"2018/10/18",216.0200,32389280.0000,217.8600,219.7400,213.0000 +"2018/10/17",221.1900,22692880.0000,222.3000,222.6400,219.3400 +"2018/10/16",222.1500,28802550.0000,218.9300,222.9900,216.7627 +"2018/10/15",217.3600,30280450.0000,221.1600,221.8300,217.2700 +"2018/10/12",222.1100,39494770.0000,220.4200,222.8800,216.8400 +"2018/10/11",214.4500,52902320.0000,214.5200,219.5000,212.3200 +"2018/10/10",216.3600,41084070.0000,225.4600,226.3500,216.0500 +"2018/10/09",226.8700,26656630.0000,223.6400,227.2700,222.2462 diff --git a/spec/technical_analysis/indicators/adi_spec.rb b/spec/technical_analysis/indicators/adi_spec.rb index 6ef9179..f6c0b9d 100644 --- a/spec/technical_analysis/indicators/adi_spec.rb +++ b/spec/technical_analysis/indicators/adi_spec.rb @@ -1,52 +1,83 @@ require 'technical-analysis' +require 'spec_helper' describe 'Indicators' do describe "ADI" do - input_data = { - "2018-12-31": {open: 157.74, high: 158.74, low: 154.74, close: 158.14, volume: 1000}, - "2018-12-28": {open: 156.23, high: 156.23, low: 156.23, close: 156.23, volume: 1000}, - "2018-12-27": {open: 156.15, high: 156.15, low: 156.15, close: 156.15, volume: 1000}, - "2018-12-26": {open: 157.17, high: 157.17, low: 157.17, close: 157.17, volume: 1000}, - "2018-12-24": {open: 146.83, high: 146.83, low: 146.83, close: 146.83, volume: 1000}, - "2018-12-21": {open: 150.73, high: 150.73, low: 150.73, close: 150.73, volume: 1000}, - "2018-12-20": {open: 156.83, high: 156.83, low: 156.83, close: 156.83, volume: 1000}, - "2018-12-19": {open: 160.89, high: 160.89, low: 160.89, close: 160.89, volume: 1000}, - "2018-12-18": {open: 166.07, high: 166.07, low: 166.07, close: 166.07, volume: 1000}, - "2018-12-17": {open: 163.94, high: 163.94, low: 163.94, close: 163.94, volume: 1000}, - "2018-12-14": {open: 165.48, high: 165.48, low: 165.48, close: 165.48, volume: 1000}, - "2018-12-13": {open: 170.95, high: 170.95, low: 170.95, close: 170.95, volume: 1000}, - "2018-12-12": {open: 169.10, high: 169.10, low: 169.10, close: 169.10, volume: 1000}, - "2018-12-11": {open: 168.63, high: 168.63, low: 168.63, close: 168.63, volume: 1000}, - "2018-12-10": {open: 169.60, high: 169.60, low: 169.60, close: 169.60, volume: 1000} - } describe 'Accumulation/Distribution Index' do it 'Calculates ADI' do - output = TechnicalAnalysis::Adi.calculate(input_data) - puts output + input_data = SpecHelper.get_test_data(:volume, :high, :low, :close) + output = TechnicalAnalysis::Adi.calculate(input_data) - expected_output = { - :"2018-12-14"=>168.752, - :"2018-12-17"=>167.61999999999998, - :"2018-12-18"=>167.108, - :"2018-12-19"=>165.46599999999998, - :"2018-12-20"=>162.642, - :"2018-12-21"=>159.692, - :"2018-12-24"=>156.27, - :"2018-12-26"=>154.49, - :"2018-12-27"=>153.54199999999997, - :"2018-12-28"=>153.422, - :"2018-12-31"=>154.823 - } + expected_output = [ + {:date=>"2018/10/09", :value=>22411774.711174767}, + {:date=>"2018/10/10", :value=>-16199273.599504825}, + {:date=>"2018/10/11", :value=>-37713866.134323865}, + {:date=>"2018/10/12", :value=>-8288954.710482582}, + {:date=>"2018/10/15", :value=>-37374123.7894299}, + {:date=>"2018/10/16", :value=>-16341921.130974408}, + {:date=>"2018/10/17", :value=>-13591269.009762233}, + {:date=>"2018/10/18", :value=>-16955140.819851194}, + {:date=>"2018/10/19", :value=>-17555977.138388995}, + {:date=>"2018/10/22", :value=>-24060850.441556394}, + {:date=>"2018/10/23", :value=>9915241.57013943}, + {:date=>"2018/10/24", :value=>-25537009.286413625}, + {:date=>"2018/10/25", :value=>-16320985.571510637}, + {:date=>"2018/10/26", :value=>-17952613.497042313}, + {:date=>"2018/10/29", :value=>-22322304.45292461}, + {:date=>"2018/10/30", :value=>-9048353.606900878}, + {:date=>"2018/10/31", :value=>-2596414.5729579534}, + {:date=>"2018/11/01", :value=>47686098.74235708}, + {:date=>"2018/11/02", :value=>2052056.5039138943}, + {:date=>"2018/11/05", :value=>8638028.433174696}, + {:date=>"2018/11/06", :value=>20488006.51898352}, + {:date=>"2018/11/07", :value=>52544543.51729703}, + {:date=>"2018/11/08", :value=>53370009.30364728}, + {:date=>"2018/11/09", :value=>59576412.70790268}, + {:date=>"2018/11/12", :value=>14980297.36136794}, + {:date=>"2018/11/13", :value=>-19025685.861899547}, + {:date=>"2018/11/14", :value=>-67251111.0548819}, + {:date=>"2018/11/15", :value=>-31201198.431607798}, + {:date=>"2018/11/16", :value=>-13921718.702957403}, + {:date=>"2018/11/19", :value=>-42863658.35269459}, + {:date=>"2018/11/20", :value=>-77157217.68155372}, + {:date=>"2018/11/21", :value=>-104408223.70305927}, + {:date=>"2018/11/23", :value=>-126035061.64521725}, + {:date=>"2018/11/26", :value=>-87657844.24649628}, + {:date=>"2018/11/27", :value=>-57716487.89688186}, + {:date=>"2018/11/28", :value=>-16831219.815120786}, + {:date=>"2018/11/29", :value=>-28229849.61904212}, + {:date=>"2018/11/30", :value=>-30619198.70995107}, + {:date=>"2018/12/03", :value=>7310177.429459017}, + {:date=>"2018/12/04", :value=>-28184142.065140743}, + {:date=>"2018/12/06", :value=>13345403.439446371}, + {:date=>"2018/12/07", :value=>-25774650.00158758}, + {:date=>"2018/12/10", :value=>27031122.187761355}, + {:date=>"2018/12/11", :value=>12348220.058324995}, + {:date=>"2018/12/12", :value=>-21169236.217537448}, + {:date=>"2018/12/13", :value=>-23482456.813564237}, + {:date=>"2018/12/14", :value=>-59826989.44514345}, + {:date=>"2018/12/17", :value=>-84453563.11062376}, + {:date=>"2018/12/18", :value=>-82088668.90680213}, + {:date=>"2018/12/19", :value=>-109189734.6005822}, + {:date=>"2018/12/20", :value=>-144651314.99705797}, + {:date=>"2018/12/21", :value=>-215519041.4917826}, + {:date=>"2018/12/24", :value=>-249091249.23371798}, + {:date=>"2018/12/26", :value=>-191621153.9435182}, + {:date=>"2018/12/27", :value=>-149563792.60023466}, + {:date=>"2018/12/28", :value=>-155977335.67328295}, + {:date=>"2018/12/31", :value=>-160289759.42328274}, + {:date=>"2019/01/02", :value=>-139000081.24146464}, + {:date=>"2019/01/03", :value=>-220800308.5532927}, + {:date=>"2019/01/04", :value=>-170386118.17770624}, + {:date=>"2019/01/07", :value=>-149339794.90125585}, + {:date=>"2019/01/08", :value=>-135060226.53761944}, + {:date=>"2019/01/09", :value=>-112451134.66006838} + ] expect(output).to eq(expected_output) end - - it "Throws exception if not enough data" do - calc = Calculate.new - expect {TechnicalAnalysis::Sma.calculate(input_data, period: 30)}.to raise_exception(Validation::ValidationError) - end end end end diff --git a/spec/technical_analysis/indicators/sma_spec.rb b/spec/technical_analysis/indicators/sma_spec.rb index 5c8dd29..2b24399 100644 --- a/spec/technical_analysis/indicators/sma_spec.rb +++ b/spec/technical_analysis/indicators/sma_spec.rb @@ -1,49 +1,81 @@ require 'technical-analysis' +require 'spec_helper' describe 'Indicators' do describe "SMA" do - input_data = { - "2018-12-31": 157.74, - "2018-12-28": 156.23, - "2018-12-27": 156.15, - "2018-12-26": 157.17, - "2018-12-24": 146.83, - "2018-12-21": 150.73, - "2018-12-20": 156.83, - "2018-12-19": 160.89, - "2018-12-18": 166.07, - "2018-12-17": 163.94, - "2018-12-14": 165.48, - "2018-12-13": 170.95, - "2018-12-12": 169.1, - "2018-12-11": 168.63, - "2018-12-10": 169.6 - } + input_data = SpecHelper.get_test_data(:close) describe 'Simple Moving Average' do it 'Calculates SMA (5 day)' do - output = TechnicalAnalysis::Sma.calculate(input_data, period: 5) + output = TechnicalAnalysis::Sma.calculate(input_data, period: 5, price_key: :close) - expected_output = { - :"2018-12-14"=>168.752, - :"2018-12-17"=>167.61999999999998, - :"2018-12-18"=>167.108, - :"2018-12-19"=>165.46599999999998, - :"2018-12-20"=>162.642, - :"2018-12-21"=>159.692, - :"2018-12-24"=>156.27, - :"2018-12-26"=>154.49, - :"2018-12-27"=>153.54199999999997, - :"2018-12-28"=>153.422, - :"2018-12-31"=>154.824 - } + expected_output = [ + {:date=>"2018/10/15", :value=>219.43}, + {:date=>"2018/10/16", :value=>218.48600000000002}, + {:date=>"2018/10/17", :value=>219.452}, + {:date=>"2018/10/18", :value=>219.766}, + {:date=>"2018/10/19", :value=>219.206}, + {:date=>"2018/10/22", :value=>219.86400000000003}, + {:date=>"2018/10/23", :value=>219.97999999999996}, + {:date=>"2018/10/24", :value=>218.76}, + {:date=>"2018/10/25", :value=>219.51600000000002}, + {:date=>"2018/10/26", :value=>218.914}, + {:date=>"2018/10/29", :value=>217.23200000000003}, + {:date=>"2018/10/30", :value=>215.346}, + {:date=>"2018/10/31", :value=>216.1}, + {:date=>"2018/11/01", :value=>216.584}, + {:date=>"2018/11/02", :value=>214.82000000000002}, + {:date=>"2018/11/05", :value=>212.69}, + {:date=>"2018/11/06", :value=>210.78400000000002}, + {:date=>"2018/11/07", :value=>209.002}, + {:date=>"2018/11/08", :value=>206.256}, + {:date=>"2018/11/09", :value=>205.654}, + {:date=>"2018/11/12", :value=>204.17000000000002}, + {:date=>"2018/11/13", :value=>201.862}, + {:date=>"2018/11/14", :value=>197.23200000000003}, + {:date=>"2018/11/15", :value=>193.816}, + {:date=>"2018/11/16", :value=>191.628}, + {:date=>"2018/11/19", :value=>189.96599999999998}, + {:date=>"2018/11/20", :value=>186.916}, + {:date=>"2018/11/21", :value=>184.91199999999998}, + {:date=>"2018/11/23", :value=>181.088}, + {:date=>"2018/11/26", :value=>177.30599999999998}, + {:date=>"2018/11/27", :value=>174.982}, + {:date=>"2018/11/28", :value=>175.77400000000003}, + {:date=>"2018/11/29", :value=>176.32799999999997}, + {:date=>"2018/11/30", :value=>177.58599999999998}, + {:date=>"2018/12/03", :value=>179.62600000000003}, + {:date=>"2018/12/04", :value=>180.11600000000004}, + {:date=>"2018/12/06", :value=>178.872}, + {:date=>"2018/12/07", :value=>176.66}, + {:date=>"2018/12/10", :value=>174.864}, + {:date=>"2018/12/11", :value=>171.626}, + {:date=>"2018/12/12", :value=>170.108}, + {:date=>"2018/12/13", :value=>169.35399999999998}, + {:date=>"2018/12/14", :value=>168.752}, + {:date=>"2018/12/17", :value=>167.61999999999998}, + {:date=>"2018/12/18", :value=>167.108}, + {:date=>"2018/12/19", :value=>165.46599999999998}, + {:date=>"2018/12/20", :value=>162.642}, + {:date=>"2018/12/21", :value=>159.692}, + {:date=>"2018/12/24", :value=>156.27}, + {:date=>"2018/12/26", :value=>154.49}, + {:date=>"2018/12/27", :value=>153.54199999999997}, + {:date=>"2018/12/28", :value=>153.422}, + {:date=>"2018/12/31", :value=>154.824}, + {:date=>"2019/01/02", :value=>157.04199999999997}, + {:date=>"2019/01/03", :value=>154.046}, + {:date=>"2019/01/04", :value=>152.468}, + {:date=>"2019/01/07", :value=>150.808}, + {:date=>"2019/01/08", :value=>149.41}, + {:date=>"2019/01/09", :value=>148.488} + ] expect(output).to eq(expected_output) end it "Throws exception if not enough data" do - calc = Calculate.new - expect {TechnicalAnalysis::Sma.calculate(input_data, period: 30)}.to raise_exception(Validation::ValidationError) + expect {TechnicalAnalysis::Sma.calculate(input_data, period: input_data.size+1, price_key: :close)}.to raise_exception(Validation::ValidationError) end end end From 10888da7b3886b56f57e6b95d357d0173bc943d1 Mon Sep 17 00:00:00 2001 From: John Feustel Date: Thu, 10 Jan 2019 16:17:19 -0700 Subject: [PATCH 3/3] Updated yard comments --- lib/technical_analysis/indicators/adi.rb | 2 +- lib/technical_analysis/indicators/sma.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/technical_analysis/indicators/adi.rb b/lib/technical_analysis/indicators/adi.rb index c5a00d9..310e14a 100644 --- a/lib/technical_analysis/indicators/adi.rb +++ b/lib/technical_analysis/indicators/adi.rb @@ -4,7 +4,7 @@ class Adi # https://en.wikipedia.org/wiki/Accumulation/distribution_index # # @param data [Array] Array of hashes with keys (:date, :high, :low, :close, :volume) - # @return [Hash] A hash of date strings to ADI values + # @return [Hash] A hash of the results with keys (:date, :value) def self.calculate(data) Validation.validate_numeric_data(data, :high, :low, :close, :volume) diff --git a/lib/technical_analysis/indicators/sma.rb b/lib/technical_analysis/indicators/sma.rb index dce56af..ec6c2f5 100644 --- a/lib/technical_analysis/indicators/sma.rb +++ b/lib/technical_analysis/indicators/sma.rb @@ -6,7 +6,7 @@ class Sma # @param data [Array] Array of hashes with keys (:date, :value) # @param period [Integer] The given period to calculate the SMA # @param price_key [Symbol] The hash key for the price data. Default :value - # @return [Hash] A hash with keys :date and :value + # @return [Hash] A hash of the results with keys (:date, :value) def self.calculate(data, period: 30, price_key: :value) Validation.validate_numeric_data(data, price_key) Validation.validate_length(data, period)