lib/sqa/stock.rb in sqa-0.0.15 vs lib/sqa/stock.rb in sqa-0.0.17

- old
+ new

@@ -4,87 +4,147 @@ # SMELL: SQA::Stock is now pretty coupled to the Alpha Vantage # API service. Should that stuff be extracted into a # separate class and injected by the requiring program? class SQA::Stock + extend Forwardable + CONNECTION = Faraday.new(url: "https://www.alphavantage.co") - attr_accessor :company_name - attr_accessor :df # The DataFrane - attr_accessor :ticker - attr_accessor :type # type of data store (default is CSV) - attr_accessor :indicators + attr_accessor :data # General Info -- SQA::DataFrame::Data + attr_accessor :df # Historical Prices -- SQA::DataFrame::Data + attr_accessor :klass # class of historical and current prices + attr_accessor :transformers # procs for changing column values from String to Numeric + def initialize( ticker:, - source: :alpha_vantage, - type: :csv + source: :alpha_vantage ) + + @ticker = ticker.downcase + @source = source + raise "Invalid Ticker #{ticker}" unless SQA::Ticker.valid?(ticker) - # TODO: Change API on lookup to return array instead of hash - # Could this also incorporate the validation process to - # save an additiona hash lookup? + @data_path = SQA.data_dir + "#{@ticker}.json" + @df_path = SQA.data_dir + "#{@ticker}.csv" - entry = SQA::Ticker.lookup(ticker) + @klass = "SQA::DataFrame::#{@source.to_s.camelize}".constantize + @transformers = "SQA::DataFrame::#{@source.to_s.camelize}::TRANSFORMERS".constantize - @ticker = ticker.downcase - @company_name = entry[:name] - @exchange = entry[:exchange] - @klass = "SQA::DataFrame::#{source.to_s.camelize}".constantize - @type = type - @indicators = OpenStruct.new + if @data_path.exist? + load + else + create + update + save + end update_the_dataframe end - def update_the_dataframe - df1 = @klass.load(@ticker, type) - df2 = @klass.recent(@ticker) + def load + @data = SQA::DataFrame::Data.new( + JSON.parse(@data_path.read) + ) + end - df1_nrows = df1.nrows - @df = @klass.append(df1, df2) - if @df.nrows > df1_nrows - @df.send("to_#{@type}", SQA.data_dir + "#{ticker}.csv") + def create + @data = + SQA::DataFrame::Data.new( + { + ticker: @ticker, + source: @source, + indicators: { xyzzy: "Magic" }, + } + ) + end + + + def update + merge_overview + end + + + def save + @data_path.write @data.to_json + end + + + def_delegator :@data, :ticker, :ticker + def_delegator :@data, :name, :name + def_delegator :@data, :exchange, :exchange + def_delegator :@data, :source, :source + def_delegator :@data, :indicators, :indicators + def_delegator :@data, :indicators=, :indicators= + def_delegator :@data, :overview, :overview + + + + def update_the_dataframe + if @df_path.exist? + @df = SQA::DataFrame.load( + source: @df_path, + transformers: @transformers + ) + else + @df = klass.recent(@ticker, full: true) + @df.to_csv(@df_path) + return end - # Adding a ticker vector in case I want to do - # some multi-stock analysis in the same data frame. - # For example to see how one stock coorelates with another. - @df[:ticker] = @ticker + from_date = Date.parse(@df.timestamp.last) + 1 + df2 = klass.recent(@ticker, from_date: from_date) + + return if df2.nil? # CSV file is up to date. + + df_nrows = @df.nrows + @df.append(df2) + + if @df.nrows > df_nrows + @df.to_csv(file_path) + end end + def to_s "#{ticker} with #{@df.size} data points from #{@df.timestamp.first} to #{@df.timestamp.last}" end + alias_method :inspect, :to_s - # TODO: Turn this into a class Stock::Overview - # which is a sub-class of Hashie::Dash - def overview - return @overview unless @overview.nil? + def merge_overview temp = JSON.parse( - CONNECTION.get("/query?function=OVERVIEW&symbol=#{@ticker.upcase}&apikey=#{Nenv.av_api_key}") + CONNECTION.get("/query?function=OVERVIEW&symbol=#{ticker.upcase}&apikey=#{SQA.av.key}") .to_hash[:body] ) + if temp.has_key? "Information" + ApiError.raise(temp["Information"]) + end + # TODO: CamelCase hash keys look common in Alpha Vantage # JSON; look at making a special Hashie-based class # to convert the keys to normal Ruby standards. temp2 = {} - string_values = %w[ address asset_type cik country currency description dividend_date ex_dividend_date exchange fiscal_year_end industry latest_quarter name sector symbol ] + string_values = %w[ address asset_type cik country currency + description dividend_date ex_dividend_date + exchange fiscal_year_end industry latest_quarter + name sector symbol + ] temp.keys.each do |k| new_k = k.underscore temp2[new_k] = string_values.include?(new_k) ? temp[k] : temp[k].to_f end - @overview = Hashie::Mash.new temp2 + @data.overview = temp2 end ############################################# ## Class Methods @@ -98,10 +158,10 @@ def top return @@top unless @@top.nil? a_hash = JSON.parse( CONNECTION.get( - "/query?function=TOP_GAINERS_LOSERS&apikey=#{Nenv.av_api_key}" + "/query?function=TOP_GAINERS_LOSERS&apikey=#{SQA.av.key}" ).to_hash[:body] ) mash = Hashie::Mash.new(a_hash)