sqa 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/checksums/sqa-0.0.13.gem.sha512 +1 -0
- data/checksums/sqa-0.0.14.gem.sha512 +1 -0
- data/lib/sqa/cli.rb +1 -0
- data/lib/sqa/data_frame/alpha_vantage.rb +155 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +3 -0
- data/lib/sqa/data_frame.rb +10 -15
- data/lib/sqa/stock.rb +100 -10
- data/lib/sqa/ticker.rb +68 -0
- data/lib/sqa/version.rb +1 -1
- data/lib/sqa.rb +15 -23
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41df647860d7465185b8f069d1fb51edcb540d4114cfd56c898e3373426e0bb8
|
4
|
+
data.tar.gz: 130b1023a4530a645ea90bf323c16dd4fb22a887222e073209b8edbfeeb6731f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b28c7bf4231e3ced4046e83f5afd256a0dc155500493b8d2c3cee43695e8ef203534a0642dc4028830f3d068653d6a9016a785b3343611a18209cb1cfb6f4694
|
7
|
+
data.tar.gz: 20ee19fe13781e473953ed4fcae73ce8e687ce1808fc74fa02d834364b7b02df14c7dab4cc0390efbae0f48328d71f27aab96318fad28052588d7b623abf4fe3
|
@@ -0,0 +1 @@
|
|
1
|
+
524fd82ea9501f1b1b1a3bf3e0f1a500e675d3f4664d342b8b377995f310c1e40a597f61d8e0bf8d2fa8dd83202f7a415b517df371dbf370c25f59fb0fb42d96
|
@@ -0,0 +1 @@
|
|
1
|
+
c5eea4fb74e6ab7b7b1715a81598585ec540a3eb1e9b902ca339ff0a2ba9cc7624ae3f43e00b5fbbad1d3d510ed938e8f91154c0bee2e14d6503564b1b0ccfb1
|
data/lib/sqa/cli.rb
CHANGED
@@ -0,0 +1,155 @@
|
|
1
|
+
# lib/sqa/data_frame/alpha_vantage.rb
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# Using the Alpha Vantage JSON interface
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'faraday'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
class SQA::DataFrame < Daru::DataFrame
|
11
|
+
class AlphaVantage
|
12
|
+
API_KEY = Nenv.av_api_key
|
13
|
+
CONNECTION = Faraday.new(url: 'https://www.alphavantage.co')
|
14
|
+
HEADERS = YahooFinance::HEADERS
|
15
|
+
|
16
|
+
# The Alpha Vantage headers are being remapped so that
|
17
|
+
# they match those of the Yahoo Finance CSV file.
|
18
|
+
#
|
19
|
+
HEADER_MAPPING = {
|
20
|
+
"date" => HEADERS[0],
|
21
|
+
"open" => HEADERS[1],
|
22
|
+
"high" => HEADERS[2],
|
23
|
+
"low" => HEADERS[3],
|
24
|
+
"close" => HEADERS[4],
|
25
|
+
"adjusted_close" => HEADERS[5],
|
26
|
+
"volume" => HEADERS[6]
|
27
|
+
}
|
28
|
+
|
29
|
+
|
30
|
+
################################################################
|
31
|
+
# Load a Dataframe from a csv file
|
32
|
+
def self.load(ticker, type="csv")
|
33
|
+
filepath = SQA.data_dir + "#{ticker}.#{type}"
|
34
|
+
|
35
|
+
if filepath.exist?
|
36
|
+
df = normalize_vector_names SQA::DataFrame.load(ticker, type)
|
37
|
+
else
|
38
|
+
df = recent(ticker, full: true)
|
39
|
+
df.send("to_#{type}",filepath)
|
40
|
+
end
|
41
|
+
|
42
|
+
df
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Normalize the vector (aka column) names as
|
47
|
+
# symbols using the standard names set by
|
48
|
+
# Yahoo Finance ... since it was the first one
|
49
|
+
# not because its anything special.
|
50
|
+
#
|
51
|
+
def self.normalize_vector_names(df)
|
52
|
+
headers = df.vectors.to_a
|
53
|
+
|
54
|
+
# convert vector names to symbols
|
55
|
+
# when they are strings. They become stings
|
56
|
+
# when the data frame is saved to a CSV file
|
57
|
+
# and then loaded back in.
|
58
|
+
|
59
|
+
if headers.first == HEADERS.first.to_s
|
60
|
+
a_hash = {}
|
61
|
+
HEADERS.each {|k| a_hash[k.to_s] = k}
|
62
|
+
df.rename_vectors(a_hash) # renames from String to Symbol
|
63
|
+
else
|
64
|
+
df.rename_vectors(HEADER_MAPPING)
|
65
|
+
end
|
66
|
+
|
67
|
+
df
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Get recent data from JSON API
|
72
|
+
#
|
73
|
+
# ticker String the security to retrieve
|
74
|
+
# returns a DataFrame
|
75
|
+
#
|
76
|
+
# NOTE: The function=TIME_SERIES_DAILY_ADJUSTED
|
77
|
+
# is not a free API endpoint from Alpha Vantange.
|
78
|
+
# So we are just using the free API endpoint
|
79
|
+
# function=TIME_SERIES_DAILY
|
80
|
+
# This means that we are not getting the
|
81
|
+
# real adjusted closing price. To sync
|
82
|
+
# the columns with those from Yahoo Finance
|
83
|
+
# we are duplicating the unadjusted clossing price
|
84
|
+
# and adding that to the data frame as if it were
|
85
|
+
# adjusted.
|
86
|
+
#
|
87
|
+
def self.recent(ticker, full: false)
|
88
|
+
# NOTE: Using the CSV format because the JSON format has
|
89
|
+
# really silly key values. The column names for the
|
90
|
+
# CSV format are much better.
|
91
|
+
response = CONNECTION.get(
|
92
|
+
"/query?" +
|
93
|
+
"function=TIME_SERIES_DAILY&" +
|
94
|
+
"symbol=#{ticker.upcase}&" +
|
95
|
+
"apikey=#{API_KEY}&" +
|
96
|
+
"datatype=csv&" +
|
97
|
+
"outputsize=#{full ? 'full' : 'compact'}"
|
98
|
+
).to_hash
|
99
|
+
|
100
|
+
unless 200 == response[:status]
|
101
|
+
raise "Bad Response: #{response[:status]}"
|
102
|
+
end
|
103
|
+
|
104
|
+
raw = response[:body].split
|
105
|
+
|
106
|
+
headers = raw.shift.split(',')
|
107
|
+
headers[0] = 'date' # website returns "timestamp" but that
|
108
|
+
# has an unintended side-effect when
|
109
|
+
# the names are normalized.
|
110
|
+
|
111
|
+
close_inx = headers.size - 2
|
112
|
+
adj_close_inx = close_inx + 1
|
113
|
+
|
114
|
+
headers.insert(adj_close_inx, 'adjusted_close')
|
115
|
+
|
116
|
+
data = raw.map do |e|
|
117
|
+
e2 = e.split(',')
|
118
|
+
e2[1..-2] = e2[1..-2].map(&:to_f) # converting open, high, low, close
|
119
|
+
e2[-1] = e2[-1].to_i # converting volumn
|
120
|
+
e2.insert(adj_close_inx, e2[close_inx]) # duplicate the close price as a fake adj close price
|
121
|
+
headers.zip(e2).to_h
|
122
|
+
end
|
123
|
+
|
124
|
+
# What oldest data first in the data frame
|
125
|
+
normalize_vector_names Daru::DataFrame.new(data.reverse)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Append update_df rows to the base_df
|
130
|
+
#
|
131
|
+
# base_df is ascending on timestamp
|
132
|
+
# update_df is descending on timestamp
|
133
|
+
#
|
134
|
+
# base_df content came from CSV file downloaded
|
135
|
+
# from Yahoo Finance.
|
136
|
+
#
|
137
|
+
# update_df came from scraping the webpage
|
138
|
+
# at Yahoo Finance for the recent history.
|
139
|
+
#
|
140
|
+
# Returns a combined DataFrame.
|
141
|
+
#
|
142
|
+
def self.append(base_df, updates_df)
|
143
|
+
last_timestamp = Date.parse base_df.timestamp.last
|
144
|
+
filtered_df = updates_df.filter_rows { |row| Date.parse(row[:timestamp]) > last_timestamp }
|
145
|
+
|
146
|
+
last_inx = filtered_df.size - 1
|
147
|
+
|
148
|
+
(0..last_inx).each do |x|
|
149
|
+
base_df.add_row filtered_df.row[last_inx-x]
|
150
|
+
end
|
151
|
+
|
152
|
+
base_df
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -59,6 +59,9 @@ class SQA::DataFrame < Daru::DataFrame
|
|
59
59
|
response = CONNECTION.get("/quote/#{ticker.upcase}/history")
|
60
60
|
doc = Nokogiri::HTML(response.body)
|
61
61
|
table = doc.css('table').first
|
62
|
+
|
63
|
+
raise "NoDataError" if table.nil?
|
64
|
+
|
62
65
|
rows = table.css('tbody tr')
|
63
66
|
|
64
67
|
data = []
|
data/lib/sqa/data_frame.rb
CHANGED
@@ -2,17 +2,19 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require_relative 'data_frame/yahoo_finance'
|
5
|
+
require_relative 'data_frame/alpha_vantage'
|
5
6
|
|
6
7
|
class Daru::DataFrame
|
7
8
|
|
8
9
|
def to_csv(path_to_file, opts={})
|
9
10
|
options = {
|
11
|
+
headers: true,
|
10
12
|
converters: :numeric
|
11
13
|
}.merge(opts)
|
12
14
|
|
13
15
|
writer = ::CSV.open(path_to_file, 'wb')
|
14
16
|
|
15
|
-
writer << vectors.to_a
|
17
|
+
writer << vectors.to_a if options[:headers]
|
16
18
|
|
17
19
|
each_row do |row|
|
18
20
|
writer << if options[:convert_comma]
|
@@ -31,27 +33,20 @@ end
|
|
31
33
|
|
32
34
|
class SQA::DataFrame < Daru::DataFrame
|
33
35
|
|
34
|
-
|
35
36
|
#################################################
|
37
|
+
def self.load(ticker, type="csv", options={}, &block)
|
38
|
+
source = SQA.data_dir + "#{ticker}.#{type}"
|
36
39
|
|
37
|
-
|
38
|
-
Pathname.new(SQA.config.data_dir) + filename
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.load(filename, options={}, &block)
|
42
|
-
source = path(filename)
|
43
|
-
type = source.extname.downcase
|
44
|
-
|
45
|
-
if ".csv" == type
|
40
|
+
if :csv == type
|
46
41
|
from_csv(source, options={}, &block)
|
47
|
-
elsif
|
42
|
+
elsif :json == type
|
48
43
|
from_json(source, options={}, &block)
|
49
|
-
elsif %
|
44
|
+
elsif %i[txt dat].include?(type)
|
50
45
|
from_plaintext(source, options={}, &block)
|
51
|
-
elsif
|
46
|
+
elsif :xls == type
|
52
47
|
from_excel(source, options={}, &block)
|
53
48
|
else
|
54
|
-
raise SQA::
|
49
|
+
raise SQA::BadParameterError, "un-supported file type: #{type}"
|
55
50
|
end
|
56
51
|
end
|
57
52
|
end
|
data/lib/sqa/stock.rb
CHANGED
@@ -1,17 +1,40 @@
|
|
1
1
|
# lib/sqa/stock.rb
|
2
2
|
|
3
|
+
require 'active_support/core_ext/string' # for String#underscore
|
4
|
+
require 'hashie' # for Hashie::Mash
|
5
|
+
|
6
|
+
|
7
|
+
# SMELL: SQA::Stock is now pretty coupled to the Alpha Vantage
|
8
|
+
# API service. Should that stuff be extracted into a
|
9
|
+
# separate class and injected by the requiring program?
|
10
|
+
|
3
11
|
class SQA::Stock
|
12
|
+
CONNECTION = Faraday.new(url: "https://www.alphavantage.co")
|
13
|
+
|
4
14
|
attr_accessor :company_name
|
5
15
|
attr_accessor :df # The DataFrane
|
6
16
|
attr_accessor :ticker
|
17
|
+
attr_accessor :type # type of data store (default is CSV)
|
7
18
|
attr_accessor :indicators
|
8
19
|
|
9
|
-
def initialize(
|
20
|
+
def initialize(
|
21
|
+
ticker:,
|
22
|
+
source: :alpha_vantage,
|
23
|
+
type: :csv
|
24
|
+
)
|
25
|
+
raise "Invalid Ticker #{ticker}" unless SQA::Ticker.valid?(ticker)
|
26
|
+
|
27
|
+
# TODO: Change API on lookup to return array instead of hash
|
28
|
+
# Could this also incorporate the validation process to
|
29
|
+
# save an additiona hash lookup?
|
30
|
+
|
31
|
+
entry = SQA::Ticker.lookup(ticker)
|
32
|
+
|
10
33
|
@ticker = ticker.downcase
|
11
|
-
@company_name =
|
34
|
+
@company_name = entry[:name]
|
35
|
+
@exchange = entry[:exchange]
|
12
36
|
@klass = "SQA::DataFrame::#{source.to_s.camelize}".constantize
|
13
37
|
@type = type
|
14
|
-
@filename = "#{@ticker}.#{type}"
|
15
38
|
@indicators = OpenStruct.new
|
16
39
|
|
17
40
|
update_the_dataframe
|
@@ -19,24 +42,91 @@ class SQA::Stock
|
|
19
42
|
|
20
43
|
|
21
44
|
def update_the_dataframe
|
22
|
-
df1 = @klass.load(@
|
45
|
+
df1 = @klass.load(@ticker, type)
|
23
46
|
df2 = @klass.recent(@ticker)
|
24
|
-
@df = @klass.append(df1, df2)
|
25
47
|
|
26
|
-
|
27
|
-
|
48
|
+
df1_nrows = df1.nrows
|
49
|
+
@df = @klass.append(df1, df2)
|
50
|
+
|
51
|
+
if @df.nrows > df1_nrows
|
52
|
+
@df.send("to_#{@type}", SQA.data_dir + "#{ticker}.csv")
|
28
53
|
end
|
29
54
|
|
30
55
|
# Adding a ticker vector in case I want to do
|
31
56
|
# some multi-stock analysis in the same data frame.
|
57
|
+
# For example to see how one stock coorelates with another.
|
32
58
|
@df[:ticker] = @ticker
|
33
59
|
end
|
34
60
|
|
35
61
|
def to_s
|
36
62
|
"#{ticker} with #{@df.size} data points from #{@df.timestamp.first} to #{@df.timestamp.last}"
|
37
63
|
end
|
38
|
-
end
|
39
64
|
|
40
|
-
|
65
|
+
# TODO: Turn this into a class Stock::Overview
|
66
|
+
# which is a sub-class of Hashie::Dash
|
67
|
+
def overview
|
68
|
+
return @overview unless @overview.nil?
|
69
|
+
|
70
|
+
temp = JSON.parse(
|
71
|
+
CONNECTION.get("/query?function=OVERVIEW&symbol=#{@ticker.upcase}&apikey=#{Nenv.av_api_key}")
|
72
|
+
.to_hash[:body]
|
73
|
+
)
|
74
|
+
|
75
|
+
# TODO: CamelCase hash keys look common in Alpha Vantage
|
76
|
+
# JSON; look at making a special Hashie-based class
|
77
|
+
# to convert the keys to normal Ruby standards.
|
41
78
|
|
42
|
-
|
79
|
+
temp2 = {}
|
80
|
+
|
81
|
+
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 ]
|
82
|
+
|
83
|
+
temp.keys.each do |k|
|
84
|
+
new_k = k.underscore
|
85
|
+
temp2[new_k] = string_values.include?(new_k) ? temp[k] : temp[k].to_f
|
86
|
+
end
|
87
|
+
|
88
|
+
@overview = Hashie::Mash.new temp2
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
#############################################
|
93
|
+
## Class Methods
|
94
|
+
|
95
|
+
class << self
|
96
|
+
@@top = nil
|
97
|
+
|
98
|
+
# Top Gainers, Losers and Most Active for most
|
99
|
+
# recent closed trading day.
|
100
|
+
#
|
101
|
+
def top
|
102
|
+
return @@top unless @@top.nil?
|
103
|
+
|
104
|
+
mash = Hashie::Mash.new(
|
105
|
+
JSON.parse(
|
106
|
+
CONNECTION.get(
|
107
|
+
"/query?function=TOP_GAINERS_LOSERS&apikey=#{Nenv.av_api_key}"
|
108
|
+
).to_hash[:body]
|
109
|
+
)
|
110
|
+
)
|
111
|
+
|
112
|
+
keys = mash.top_gainers.first.keys
|
113
|
+
|
114
|
+
%w[top_gainers top_losers most_actively_traded].each do |collection|
|
115
|
+
mash.send(collection).each do |e|
|
116
|
+
keys.each do |k|
|
117
|
+
case k
|
118
|
+
when 'ticker'
|
119
|
+
# Leave it as a String
|
120
|
+
when 'volume'
|
121
|
+
e[k] = e[k].to_i
|
122
|
+
else
|
123
|
+
e[k] = e[k].to_f
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
@@top = mash
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/sqa/ticker.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# sqa/lib/sqa/ticker.rb
|
2
|
+
#
|
3
|
+
# Uses the https://dumbstockapi.com/ website to download a CSV file
|
4
|
+
#
|
5
|
+
# The CSV files have names like this:
|
6
|
+
# "dumbstockapi-2023-09-21T16 39 55.165Z.csv"
|
7
|
+
#
|
8
|
+
# which has this header:
|
9
|
+
# ticker,name,is_etf,exchange
|
10
|
+
#
|
11
|
+
# Not using the is_etf columns
|
12
|
+
#
|
13
|
+
class SQA::Ticker
|
14
|
+
FILENAME_PREFIX = "dumbstockapi"
|
15
|
+
CONNECTION = Faraday.new(url: "https://dumbstockapi.com")
|
16
|
+
@@data = {}
|
17
|
+
|
18
|
+
|
19
|
+
def self.download(country="US")
|
20
|
+
response = CONNECTION.get("/stock?format=csv&countries=#{country.upcase}").to_hash
|
21
|
+
|
22
|
+
if 200 == response[:status]
|
23
|
+
filename = response[:response_headers]["content-disposition"].split('=').last.gsub('"','')
|
24
|
+
out_path = Pathname.new(SQA.config.data_dir) + filename
|
25
|
+
out_path.write response[:body]
|
26
|
+
end
|
27
|
+
|
28
|
+
response[:status]
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def self.load
|
33
|
+
tries = 0
|
34
|
+
found = false
|
35
|
+
|
36
|
+
until(found || tries >= 3) do
|
37
|
+
files = Pathname.new(SQA.config.data_dir).children.select{|c| c.basename.to_s.start_with?(FILENAME_PREFIX)}.sort
|
38
|
+
if files.empty?
|
39
|
+
download
|
40
|
+
tries += 1
|
41
|
+
else
|
42
|
+
found = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
raise "NoDataError" if files.empty?
|
47
|
+
|
48
|
+
load_from_csv files.last
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def self.load_from_csv(csv_path)
|
53
|
+
CSV.foreach(csv_path, headers: true) do |row|
|
54
|
+
@@data[row["ticker"]] = {
|
55
|
+
name: row["name"],
|
56
|
+
exchange: row["exchange"]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
@@data
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
def self.data = @@data.empty? ? load : @@data
|
66
|
+
def self.lookup(ticker) = data[ticker.upcase]
|
67
|
+
def self.valid?(ticker) = data.has_key?(ticker.upcase)
|
68
|
+
end
|
data/lib/sqa/version.rb
CHANGED
data/lib/sqa.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'active_support'
|
5
5
|
require 'active_support/core_ext/string'
|
6
|
+
require 'amazing_print'
|
6
7
|
require 'daru'
|
7
8
|
require 'date'
|
8
9
|
require 'descriptive_statistics'
|
@@ -10,6 +11,7 @@ require 'nenv'
|
|
10
11
|
require 'pathname'
|
11
12
|
|
12
13
|
require_relative "sqa/version"
|
14
|
+
require_relative "sqa/errors"
|
13
15
|
|
14
16
|
|
15
17
|
unless defined?(HOME)
|
@@ -21,6 +23,9 @@ module SQA
|
|
21
23
|
class << self
|
22
24
|
@@config = nil
|
23
25
|
|
26
|
+
# Initializes the SQA modules
|
27
|
+
# returns the configuration
|
28
|
+
#
|
24
29
|
def init(argv=ARGV)
|
25
30
|
if argv.is_a? String
|
26
31
|
argv = argv.split()
|
@@ -34,41 +39,28 @@ module SQA
|
|
34
39
|
CLI.run(argv)
|
35
40
|
else
|
36
41
|
# There are no real command line parameters
|
37
|
-
# because the sqa gem is
|
42
|
+
# because the sqa gem is being required within
|
38
43
|
# the context of a larger program.
|
39
44
|
end
|
40
45
|
|
46
|
+
config.data_dir = homify(config.data_dir)
|
47
|
+
|
41
48
|
Daru.lazy_update = config.lazy_update
|
42
49
|
Daru.plotting_library = config.plotting_library
|
43
50
|
|
44
|
-
|
45
|
-
debug_me{[
|
46
|
-
:config
|
47
|
-
]}
|
48
|
-
end
|
49
|
-
|
50
|
-
nil
|
51
|
+
config
|
51
52
|
end
|
52
53
|
|
53
|
-
def
|
54
|
-
|
55
|
-
end
|
54
|
+
def debug?() = @@config.debug?
|
55
|
+
def verbose?() = @@config.verbose?
|
56
56
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
57
|
+
def homify(filepath) = filepath.gsub(/^~/, Nenv.home)
|
58
|
+
def data_dir() = Pathname.new(config.data_dir)
|
59
|
+
def config() = @@config
|
60
60
|
|
61
61
|
def config=(an_object)
|
62
62
|
@@config = an_object
|
63
63
|
end
|
64
|
-
|
65
|
-
def debug?
|
66
|
-
@@config.debug?
|
67
|
-
end
|
68
|
-
|
69
|
-
def verbose?
|
70
|
-
@@config.verbose?
|
71
|
-
end
|
72
64
|
end
|
73
65
|
end
|
74
66
|
|
@@ -77,9 +69,9 @@ end
|
|
77
69
|
require_relative "sqa/config"
|
78
70
|
require_relative "sqa/constants"
|
79
71
|
require_relative "sqa/data_frame"
|
80
|
-
require_relative "sqa/errors"
|
81
72
|
require_relative "sqa/indicator"
|
82
73
|
require_relative "sqa/portfolio"
|
83
74
|
require_relative "sqa/strategy"
|
84
75
|
require_relative "sqa/stock"
|
76
|
+
require_relative "sqa/ticker"
|
85
77
|
require_relative "sqa/trade"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dewayne VanHoozer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-09-
|
11
|
+
date: 2023-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -281,6 +281,8 @@ files:
|
|
281
281
|
- checksums/sqa-0.0.10.gem.sha512
|
282
282
|
- checksums/sqa-0.0.11.gem.sha512
|
283
283
|
- checksums/sqa-0.0.12.gem.sha512
|
284
|
+
- checksums/sqa-0.0.13.gem.sha512
|
285
|
+
- checksums/sqa-0.0.14.gem.sha512
|
284
286
|
- checksums/sqa-0.0.2.gem.sha512
|
285
287
|
- checksums/sqa-0.0.3.gem.sha512
|
286
288
|
- checksums/sqa-0.0.4.gem.sha512
|
@@ -329,6 +331,7 @@ files:
|
|
329
331
|
- lib/sqa/config.rb
|
330
332
|
- lib/sqa/constants.rb
|
331
333
|
- lib/sqa/data_frame.rb
|
334
|
+
- lib/sqa/data_frame/alpha_vantage.rb
|
332
335
|
- lib/sqa/data_frame/yahoo_finance.rb
|
333
336
|
- lib/sqa/errors.rb
|
334
337
|
- lib/sqa/indicator.rb
|
@@ -366,6 +369,7 @@ files:
|
|
366
369
|
- lib/sqa/strategy/random.rb
|
367
370
|
- lib/sqa/strategy/rsi.rb
|
368
371
|
- lib/sqa/strategy/sma.rb
|
372
|
+
- lib/sqa/ticker.rb
|
369
373
|
- lib/sqa/trade.rb
|
370
374
|
- lib/sqa/version.rb
|
371
375
|
- lib/sqa/web.rb
|