sqa 0.0.8 → 0.0.10
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/README.md +6 -0
- data/Rakefile +9 -0
- data/checksums/sqa-0.0.10.gem.sha512 +1 -0
- data/checksums/sqa-0.0.9.gem.sha512 +1 -0
- data/lib/sqa/cli.rb +2 -2
- data/lib/sqa/config.rb +13 -3
- data/lib/sqa/data_frame/yahoo_finance.rb +91 -14
- data/lib/sqa/data_frame.rb +31 -1
- data/lib/sqa/stock.rb +18 -4
- data/lib/sqa/strategy.rb +8 -0
- data/lib/sqa/version.rb +1 -1
- data/lib/sqa.rb +7 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b699ff032eee80858f9cfc3c3cba7e1ad5c0b537cd9b014085d184f4ce052451
|
4
|
+
data.tar.gz: 3c7211690c31b6486718306d7e62fdf71c9466a421cfc694955a64e0d0184e66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8a319f9b59ce9af8cccf6ee0cd436e7847db90e95703da13ea50cf33d03cd5c5aeb996b87503f2ddc6f7991f18441be7c49a099377a19a89b6c8bd7ceb4eb3b
|
7
|
+
data.tar.gz: 9b7e5b5a16f21d256c44f8a491b6f4bc0b60cc507d9124b49b12d6da9c2e487f64b0f2388ecdc4e0d24e10eb4b4f2fd69f5ca190eb5a4f51af9aba7a1123d6ea
|
data/README.md
CHANGED
@@ -8,6 +8,12 @@ The BUY/SELL signals that it generates are part of a game. **DO NOT USE** when
|
|
8
8
|
|
9
9
|
I'm making use of lots of gems which may not be part of the gemspec at this time. I will be adding them as they make the final cut as to fitness for the intended function. Some gems are configurable. For example the default for the plotting library is `gruff`. There are several available that the `daru` gem can use.
|
10
10
|
|
11
|
+
### DARU or RedAmber
|
12
|
+
|
13
|
+
I'm just really using `daru` for its data frame object; However, I just learned about the RedAmber data frame object in Ruby based off of Apache Arrow. I'm going to look at that product since it is actively maintained.
|
14
|
+
|
15
|
+
https://github.com/red-data-tools/red_amber
|
16
|
+
|
11
17
|
## Installation
|
12
18
|
|
13
19
|
Install the gem and add to the application's Gemfile by executing:
|
data/Rakefile
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
|
+
require "minitest/test_task"
|
5
|
+
|
6
|
+
Minitest::TestTask.create(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.warning = false
|
10
|
+
t.test_globs = ["test/**/*_test.rb"]
|
11
|
+
end
|
12
|
+
|
4
13
|
task default: %i[]
|
@@ -0,0 +1 @@
|
|
1
|
+
2df18c06074d6725d838fcda7a72d5053859cc677898f799215a223720eadfc6eb393ab765b074926040b7509b6bb8e0a0b13e1181b0d1713951445a87ac7a6e
|
@@ -0,0 +1 @@
|
|
1
|
+
2a8a31c1dced7189c4b5c2a53a14fd222b28bad3de103bab088e5a1ee49c3dcb9f9632e8fa84ad11a28749477dfb9b6a79e3b86cb1cd26ac88517b27e3a5ad99
|
data/lib/sqa/cli.rb
CHANGED
@@ -57,7 +57,7 @@ module SQA
|
|
57
57
|
desc "Set the directory for the SQA data"
|
58
58
|
end
|
59
59
|
|
60
|
-
|
60
|
+
option :dump_config do
|
61
61
|
long "--dump-config path_to_file"
|
62
62
|
desc "Dump the current configuration"
|
63
63
|
end
|
@@ -156,7 +156,7 @@ module SQA
|
|
156
156
|
|
157
157
|
def remove_temps(a_hash)
|
158
158
|
temps = %i[ help version dump ]
|
159
|
-
debug_me{[ :a_hash ]}
|
159
|
+
# debug_me{[ :a_hash ]}
|
160
160
|
a_hash.reject{|k, _| temps.include? k}
|
161
161
|
end
|
162
162
|
end
|
data/lib/sqa/config.rb
CHANGED
@@ -18,7 +18,9 @@ module SQA
|
|
18
18
|
include Hashie::Extensions::Coercion
|
19
19
|
include Hashie::Extensions::Dash::PredefinedValues
|
20
20
|
|
21
|
-
property :config_file #,
|
21
|
+
property :config_file #,a String filepath for the current config overriden by cli options
|
22
|
+
property :dump_config # a String filepath into which to dump the current config
|
23
|
+
|
22
24
|
property :data_dir, default: Nenv.home + "/sqa_data"
|
23
25
|
|
24
26
|
# TODO: If no path is given, these files will be in
|
@@ -163,7 +165,15 @@ module SQA
|
|
163
165
|
def dump_json = File.open(config_file, "w") { |f| f.write JSON.pretty_generate(as_hash)}
|
164
166
|
def dump_toml = File.open(config_file, "w") { |f| f.write TomlRB.dump(as_hash)}
|
165
167
|
def dump_yaml = File.open(config_file, "w") { |f| f.write as_hash.to_yaml}
|
166
|
-
|
168
|
+
|
169
|
+
|
170
|
+
#####################################
|
171
|
+
class << self
|
172
|
+
def reset
|
173
|
+
SQA.config = new
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
167
177
|
end
|
168
178
|
|
169
|
-
SQA
|
179
|
+
SQA::Config.reset
|
@@ -1,28 +1,105 @@
|
|
1
1
|
# lib/sqa/data_frame/yahoo_finance.rb
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'faraday'
|
5
|
+
require 'nokogiri'
|
6
|
+
|
4
7
|
class SQA::DataFrame < Daru::DataFrame
|
5
8
|
class YahooFinance
|
6
|
-
|
7
|
-
|
9
|
+
CONNECTION = Faraday.new(url: 'https://finance.yahoo.com')
|
10
|
+
HEADERS = %i[
|
11
|
+
timestamp # 0
|
12
|
+
open_price # 1
|
13
|
+
high_price # 2
|
14
|
+
low_price # 3
|
15
|
+
close_price # 4
|
16
|
+
adj_close_price # 5
|
17
|
+
volume # 6
|
18
|
+
]
|
8
19
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
20
|
+
# The Yahoo Finance Headers are being remapped so that
|
21
|
+
# the header can be used as a method name to access the
|
22
|
+
# vector.
|
12
23
|
#
|
13
|
-
|
14
|
-
"Date" =>
|
15
|
-
"Open" =>
|
16
|
-
"High" =>
|
17
|
-
"Low" =>
|
18
|
-
"Close" =>
|
19
|
-
"Adj Close" =>
|
20
|
-
"Volume" =>
|
24
|
+
HEADER_MAPPING = {
|
25
|
+
"Date" => HEADERS[0],
|
26
|
+
"Open" => HEADERS[1],
|
27
|
+
"High" => HEADERS[2],
|
28
|
+
"Low" => HEADERS[3],
|
29
|
+
"Close" => HEADERS[4],
|
30
|
+
"Adj Close" => HEADERS[5],
|
31
|
+
"Volume" => HEADERS[6]
|
21
32
|
}
|
22
33
|
|
23
|
-
|
34
|
+
################################################################
|
35
|
+
def self.load(filename, options={}, &block)
|
36
|
+
df = SQA::DataFrame.load(filename, options={}, &block)
|
37
|
+
|
38
|
+
headers = df.vectors
|
39
|
+
|
40
|
+
if headers.first == HEADERS.first.to_s
|
41
|
+
a_hash = {}
|
42
|
+
HEADERS.each {|k| a_hash[k.to_s] = k}
|
43
|
+
df.rename_vectors(a_hash)
|
44
|
+
else
|
45
|
+
df.rename_vectors(HEADER_MAPPING)
|
46
|
+
end
|
24
47
|
|
25
48
|
df
|
26
49
|
end
|
50
|
+
|
51
|
+
|
52
|
+
# Scrape the Yahoo Finance website to get recent
|
53
|
+
# historical prices for a specific ticker
|
54
|
+
#
|
55
|
+
# ticker String the security to retrieve
|
56
|
+
# returns a DataFrame
|
57
|
+
#
|
58
|
+
def self.recent(ticker)
|
59
|
+
response = CONNECTION.get("/quote/#{ticker.upcase}/history")
|
60
|
+
doc = Nokogiri::HTML(response.body)
|
61
|
+
table = doc.css('table').first
|
62
|
+
rows = table.css('tbody tr')
|
63
|
+
|
64
|
+
data = []
|
65
|
+
|
66
|
+
rows.each do |row|
|
67
|
+
cols = row.css('td').map{|c| c&.text}
|
68
|
+
next if cols[1]&.include?("Dividend")
|
69
|
+
cols[0] = Date.parse(cols[0]).to_s
|
70
|
+
cols[6] = cols[6].tr(',','').to_i
|
71
|
+
(1..5).each {|x| cols[x] = cols[x].to_f}
|
72
|
+
data << HEADERS.zip(cols).to_h
|
73
|
+
end
|
74
|
+
|
75
|
+
Daru::DataFrame.new(data)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Append update_df rows to the base_df
|
80
|
+
#
|
81
|
+
# base_df is ascending on timestamp
|
82
|
+
# update_df is descending on timestamp
|
83
|
+
#
|
84
|
+
# base_df content came from CSV file downloaded
|
85
|
+
# from Yahoo Finance.
|
86
|
+
#
|
87
|
+
# update_df came from scraping the webpage
|
88
|
+
# at Yahoo Finance for the recent history.
|
89
|
+
#
|
90
|
+
# Returns a combined DataFrame.
|
91
|
+
#
|
92
|
+
def self.append(base_df, updates_df)
|
93
|
+
last_timestamp = Date.parse base_df.timestamp.last
|
94
|
+
filtered_df = updates_df.filter_rows { |row| Date.parse(row[:timestamp]) > last_timestamp }
|
95
|
+
|
96
|
+
last_inx = filtered_df.size - 1
|
97
|
+
|
98
|
+
(0..last_inx).each do |x|
|
99
|
+
base_df.add_row filtered_df.row[last_inx-x]
|
100
|
+
end
|
101
|
+
|
102
|
+
base_df
|
103
|
+
end
|
27
104
|
end
|
28
105
|
end
|
data/lib/sqa/data_frame.rb
CHANGED
@@ -3,9 +3,39 @@
|
|
3
3
|
|
4
4
|
require_relative 'data_frame/yahoo_finance'
|
5
5
|
|
6
|
+
class Daru::DataFrame
|
7
|
+
|
8
|
+
def to_csv(path_to_file, opts={})
|
9
|
+
options = {
|
10
|
+
converters: :numeric
|
11
|
+
}.merge(opts)
|
12
|
+
|
13
|
+
writer = ::CSV.open(path_to_file, 'wb')
|
14
|
+
|
15
|
+
writer << vectors.to_a unless options[:headers] == false
|
16
|
+
|
17
|
+
each_row do |row|
|
18
|
+
writer << if options[:convert_comma]
|
19
|
+
row.map { |v| v.to_s.tr('.', ',') }
|
20
|
+
else
|
21
|
+
row.to_a
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
writer.close
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
|
6
32
|
class SQA::DataFrame < Daru::DataFrame
|
33
|
+
|
34
|
+
|
35
|
+
#################################################
|
36
|
+
|
7
37
|
def self.path(filename)
|
8
|
-
SQA
|
38
|
+
Pathname.new SQA.config.data_dir + filename
|
9
39
|
end
|
10
40
|
|
11
41
|
def self.load(filename, options={}, &block)
|
data/lib/sqa/stock.rb
CHANGED
@@ -8,10 +8,24 @@ class SQA::Stock
|
|
8
8
|
def initialize(ticker:, source: :yahoo_finance, type: :csv)
|
9
9
|
@ticker = ticker.downcase
|
10
10
|
@company_name = "Company Name"
|
11
|
-
klass
|
12
|
-
|
13
|
-
@
|
14
|
-
|
11
|
+
@klass = "SQA::DataFrame::#{source.to_s.camelize}".constantize
|
12
|
+
@type = type
|
13
|
+
@filename = "#{@ticker}.#{type}"
|
14
|
+
|
15
|
+
update_the_dataframe
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def update_the_dataframe
|
20
|
+
df1 = @klass.load(@filename)
|
21
|
+
df2 = @klass.recent(@ticker)
|
22
|
+
@df = @klass.append(df1, df2)
|
23
|
+
|
24
|
+
unless @df.size == df1.size
|
25
|
+
@df.send("to_#{@type}", SQA::DataFrame.path(@filename))
|
26
|
+
end
|
27
|
+
|
28
|
+
@df[:ticker] = @ticker
|
15
29
|
end
|
16
30
|
|
17
31
|
def to_s
|
data/lib/sqa/strategy.rb
CHANGED
@@ -54,4 +54,12 @@ class SQA::Strategy
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
require_relative 'strategy/common'
|
58
|
+
require_relative 'strategy/consensus'
|
59
|
+
require_relative 'strategy/ema'
|
60
|
+
require_relative 'strategy/mp'
|
61
|
+
require_relative 'strategy/mr'
|
62
|
+
require_relative 'strategy/random'
|
63
|
+
require_relative 'strategy/rsi'
|
64
|
+
require_relative 'strategy/sma'
|
57
65
|
|
data/lib/sqa/version.rb
CHANGED
data/lib/sqa.rb
CHANGED
@@ -30,7 +30,13 @@ module SQA
|
|
30
30
|
# Ran at SQA::Config elaboration time
|
31
31
|
# @@config = Config.new
|
32
32
|
|
33
|
-
|
33
|
+
if defined? CLI
|
34
|
+
CLI.run(argv)
|
35
|
+
else
|
36
|
+
# There are no real command line parameters
|
37
|
+
# because the sqa gem is be required within
|
38
|
+
# the context of a larger program.
|
39
|
+
end
|
34
40
|
|
35
41
|
Daru.lazy_update = config.lazy_update
|
36
42
|
Daru.plotting_library = config.plotting_library
|
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.10
|
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-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -278,6 +278,7 @@ files:
|
|
278
278
|
- Rakefile
|
279
279
|
- bin/sqa
|
280
280
|
- checksums/sqa-0.0.1.gem.sha512
|
281
|
+
- checksums/sqa-0.0.10.gem.sha512
|
281
282
|
- checksums/sqa-0.0.2.gem.sha512
|
282
283
|
- checksums/sqa-0.0.3.gem.sha512
|
283
284
|
- checksums/sqa-0.0.4.gem.sha512
|
@@ -285,6 +286,7 @@ files:
|
|
285
286
|
- checksums/sqa-0.0.6.gem.sha512
|
286
287
|
- checksums/sqa-0.0.7.gem.sha512
|
287
288
|
- checksums/sqa-0.0.8.gem.sha512
|
289
|
+
- checksums/sqa-0.0.9.gem.sha512
|
288
290
|
- docs/.gitignore
|
289
291
|
- docs/README.md
|
290
292
|
- docs/average_true_range.md
|