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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ed5a0bfe672b2a11993e5afd8ed8cbb103355f033c95e791c8c2ef57dbd8464
4
- data.tar.gz: 308d76cd23b50c057816ed7cde0815a51b4bb5e196ea14e231fd68ad06fe76b5
3
+ metadata.gz: b699ff032eee80858f9cfc3c3cba7e1ad5c0b537cd9b014085d184f4ce052451
4
+ data.tar.gz: 3c7211690c31b6486718306d7e62fdf71c9466a421cfc694955a64e0d0184e66
5
5
  SHA512:
6
- metadata.gz: 1e8297f1d895d4ae7509a24486dc5ae3f542e1f0aeead4308a37da0a9a240f9b8e7bc2858914d928277044be78aff8eb6880c3b86329457e1cc329903d2faf23
7
- data.tar.gz: d58c62150ae5ed500222704cf550f7f34d7740fe4a5e7761354962ce3fbd79c21353d9d5b6a017f6b3c2e636316c745e05b261507883bace63901769b1959049
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
- flag :dump_config do
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 #, default: Nenv.home + "/.sqa.yml"
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
- end
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.config = SQA::Config.new
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
- def self.load(filename, options={}, &block)
7
- df = SQA::DataFrame.load(filename, options={}, &block)
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
- # ASSUMPTION: This is the column headers from Yahoo Finance for
10
- # CSV files. If the file type is something different from the
11
- # same source, they may not be the same.
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
- new_names = {
14
- "Date" => :timestamp,
15
- "Open" => :open_price,
16
- "High" => :high_price,
17
- "Low" => :low_price,
18
- "Close" => :close_price,
19
- "Adj Close" => :adj_close_price,
20
- "Volume" => :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
- df.rename_vectors(new_names)
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
@@ -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::Config.data_dir + filename
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 = "SQA::DataFrame::#{source.to_s.camelize}".constantize
12
- filename = "#{@ticker}.#{type}"
13
- @df = klass.send(:load, filename)
14
- @df[:ticker] = ticker
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
@@ -4,7 +4,7 @@ require 'sem_version'
4
4
  require 'sem_version/core_ext'
5
5
 
6
6
  module SQA
7
- VERSION = "0.0.8"
7
+ VERSION = "0.0.10"
8
8
 
9
9
  class << self
10
10
  def version
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
- CLI.run(argv) if defined? CLI
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.8
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-05 00:00:00.000000000 Z
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