sqa 0.0.22 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sqa/cli.rb CHANGED
@@ -1,135 +1,40 @@
1
1
  # lib/sqa/cli.rb
2
2
 
3
+ require 'dry/cli'
3
4
 
4
5
  require_relative '../sqa'
6
+ require_relative 'commands'
5
7
 
6
- # SMELL: Architectyre has become confused between CLI and Command
7
-
8
- # TODO: Fix the mess between CLI and Command
9
-
10
-
11
- module SQA
12
- class CLI
13
- include TTY::Option
14
-
15
- header "Stock Quantitative Analysis (SQA)"
16
- footer "WARNING: This is a toy, a play thing, not intended for serious use."
17
-
18
- program "sqa"
19
- desc "A collection of things"
20
-
21
- example "sqa -c ~/.sqa.yml -p portfolio.csv -t trades.csv --data-dir ~/sqa_data"
22
-
23
-
24
- option :config_file do
25
- short "-c string"
26
- long "--config string"
27
- desc "Path to the config file"
8
+ module SQA::CLI
9
+ class << self
10
+ def run!
11
+ Dry::CLI.new(SQA::Commands).call
28
12
  end
13
+ end
14
+ end
29
15
 
30
- option :log_level do
31
- short "-l string"
32
- long "--log_level string"
33
- # default SQA.config.log_level
34
- desc "Set the log level (debug, info, warn, error, fatal)"
35
- end
36
16
 
37
- option :portfolio do
38
- short "-p string"
39
- long "--portfolio string"
40
- # default SQA.config.portfolio_filename
41
- desc "Set the filename of the portfolio"
42
- end
43
17
 
44
18
 
45
- option :trades do
46
- short "-t string"
47
- long "--trades string"
48
- # default SQA.config.trades_filename
49
- desc "Set the filename into which trades are stored"
50
- end
19
+ __END__
51
20
 
52
21
 
53
- option :data_dir do
54
- long "--data-dir string"
55
- # default SQA.config.data_dir
56
- desc "Set the directory for the SQA data"
57
- end
58
22
 
59
- option :dump_config do
60
- long "--dump-config path_to_file"
61
- desc "Dump the current configuration"
62
- end
63
23
 
64
- flag :help do
65
- short "-h"
66
- long "--help"
67
- desc "Print usage"
68
- end
24
+ # header "Stock Quantitative Analysis (SQA)"
25
+ # footer "WARNING: This is a toy, a play thing, not intended for serious use."
69
26
 
70
- flag :version do
71
- long "--version"
72
- desc "Print version"
73
- end
27
+ # program "sqa"
28
+ # desc "A collection of things"
74
29
 
75
- flag :debug do
76
- short "-d"
77
- long "--debug"
78
- # default SQA.config.debug
79
- desc "Turn on debugging output"
80
- end
81
30
 
82
- flag :verbose do
83
- short "-v"
84
- long "--verbose"
85
- # default SQA.config.debug
86
- desc "Print verbosely"
87
- end
88
31
 
89
- class << self
90
- @@subclasses = []
91
- @@commands_available = []
92
-
93
- def names
94
- '['+ @@commands_available.join('|')+']'
95
- end
96
-
97
- def inherited(subclass)
98
- super
99
- @@subclasses << subclass
100
- @@commands_available << subclass.command.join
101
- end
102
-
103
- def command_descriptions
104
- help_block = "Optional Command Available:"
105
-
106
- @@commands_available.size.times do |x|
107
- klass = @@subclasses[x]
108
- help_block << "\n " + @@commands_available[x] + " - "
109
- help_block << klass.desc.join
110
- end
111
-
112
- help_block
113
- end
114
32
 
33
+ class << self
115
34
 
116
35
  ##################################################
117
36
  def run(argv = ARGV)
118
- cli = new
119
- parser = cli.parse(argv)
120
- params = parser.params
121
37
 
122
- if params[:help]
123
- print parser.help
124
- exit(0)
125
-
126
- elsif params.errors.any?
127
- puts params.errors.summary
128
- exit(1)
129
-
130
- elsif params[:version]
131
- puts SQA.version
132
- exit(0)
133
38
 
134
39
  elsif params[:dump_config]
135
40
  SQA.config.config_file = params[:dump_config]
@@ -144,30 +49,14 @@ module SQA
144
49
  SQA.config.from_file
145
50
  end
146
51
 
52
+
53
+
147
54
  # Override the defaults <- envars <- config file <- cli parameters
148
55
  SQA.config.merge!(remove_temps params.to_h)
149
56
 
150
- if SQA.debug? || SQA.verbose?
151
- debug_me("config after CLI parameters"){[
152
- "SQA.config"
153
- ]}
154
- end
155
- end
156
57
 
157
- def remove_temps(a_hash)
158
- temps = %i[ help version dump ]
159
- # debug_me{[ :a_hash ]}
160
- a_hash.reject{|k, _| temps.include? k}
161
58
  end
162
59
  end
163
60
  end
164
61
  end
165
62
 
166
- require_relative 'analysis'
167
- require_relative 'web'
168
-
169
- # First Load TTY-Option's command content with all available commands
170
- # then these have access to the entire ObjectSpace ...
171
- SQA::CLI.command SQA::CLI.names
172
- SQA::CLI.example SQA::CLI.command_descriptions
173
-
@@ -1,18 +1,26 @@
1
- # lib/sqa/command/analysis.rb
1
+ # sqa/lib/sqa/commands/analysis.rb
2
2
 
3
- module SQA
4
- class Analysis < CLI
5
- include TTY::Option
3
+ class Commands::Analysis < Commands::Base
4
+ VERSION = "0.0.1-analysis"
6
5
 
7
- command "Analysis"
6
+ Commands.register "analysis", self
8
7
 
9
- desc "Provide an Analysis of a Portfolio"
8
+ desc "Provide an Analysis of a Portfolio"
10
9
 
11
10
 
12
- def initialize
13
- # TODO: something
14
- end
11
+ def initialize
12
+ # TODO: something
15
13
  end
14
+
15
+ def call(params)
16
+ config = super
17
+
18
+ puts <<~EOS
19
+ ##################################
20
+ ## Running the Analysis Command ##
21
+ ##################################
22
+ EOS
23
+ end
16
24
  end
17
25
 
18
26
  __END__
@@ -270,11 +278,6 @@ tickers.each do |ticker|
270
278
  values << row
271
279
  end
272
280
 
273
- # debug_me{[
274
- # :result
275
- # ]}
276
-
277
-
278
281
  the_table = TTY::Table.new(headers, values)
279
282
 
280
283
  puts
@@ -0,0 +1,139 @@
1
+ # .../sqa/cli/commands/base.rb
2
+
3
+ # SQA.config will be built with its defaults
4
+ # and envar over-rides BEFORE a command is
5
+ # process. This means that options do not
6
+ # need to have a "default" value.
7
+
8
+ # Establish a Base command class that has global options
9
+ # available to all commands.
10
+
11
+ class Commands::Base < Dry::CLI::Command
12
+ # keys from Dry::Cli options which we do not want in the
13
+ # config object.
14
+ IGNORE_OPTIONS = %i[ version ]
15
+
16
+ global_header <<~EOS
17
+
18
+ SQA - Stock Quantitative Analysis
19
+ by: MadBomber
20
+
21
+ This is a work in progress. It is not fit for anything
22
+ other than play time. ** Do not ** use it to make any
23
+ kind of serious trading decisions.
24
+
25
+ EOS
26
+
27
+ global_footer <<~EOS
28
+
29
+ SARNING: This product is a work in progress. DO NOT USE
30
+ for serious trading decisions.
31
+
32
+ Copyright (c) 2023 - MadBomber Software
33
+
34
+ EOS
35
+
36
+ option :debug,
37
+ required: false,
38
+ type: :boolean,
39
+ desc: 'Print debug information',
40
+ aliases: %w[-d --debug]
41
+
42
+ option :verbose,
43
+ required: false,
44
+ type: :boolean,
45
+ desc: 'Print verbose information',
46
+ aliases: %w[-v --verbose]
47
+
48
+
49
+ option :version,
50
+ required: false,
51
+ type: :boolean,
52
+ default: false,
53
+ desc: 'Print version(s) and exit',
54
+ aliases: %w[--version]
55
+
56
+
57
+ option :config_file,
58
+ required: false,
59
+ type: :string,
60
+ desc: "Path to the config file"
61
+
62
+
63
+ option :log_level,
64
+ required: false,
65
+ type: :string,
66
+ values: %w[debug info warn error fatal ],
67
+ desc: "Set the log level"
68
+
69
+
70
+ option :portfolio,
71
+ required: false,
72
+ aliases: %w[ --portfolio --folio --file -f ],
73
+ type: :string,
74
+ desc: "Set the filename of the portfolio"
75
+
76
+
77
+ option :trades,
78
+ required: false,
79
+ aliases: %w[ --trades ],
80
+ type: :string,
81
+ desc: "Set the filename into which trades are stored"
82
+
83
+
84
+ option :data_dir,
85
+ required: false,
86
+ aliases: %w[ --data-dir --data --dir ],
87
+ type: :string,
88
+ desc: "Set the directory for the SQA data"
89
+
90
+
91
+ option :dump_config,
92
+ required: false,
93
+ type: :string,
94
+ desc: "Dump the current configuration to a file"
95
+
96
+
97
+ # All command class call methods should start with
98
+ # super so that this method is invoked.
99
+ #
100
+ # params is a Hash from Dry::CLI where keys are Symbol
101
+
102
+ def call(params)
103
+ show_versions_and_exit if params[:version]
104
+
105
+ unless params[:config_file].nil? || params[:config_file].empty?
106
+ SQA.config.config_file = params[:config_file]
107
+ SQA.config.from_file
108
+ end
109
+
110
+ update_config(params)
111
+
112
+ unless params[:dump_config].nil? || params[:dump_config].empty?
113
+ SQA.config.config_file = params[:dump_config]
114
+ SQA.config.dump_file
115
+ end
116
+
117
+ SQA.config
118
+ end
119
+
120
+ ################################################
121
+ private
122
+
123
+ def show_versions_and_exit
124
+ self.class.ancestors.each do |ancestor|
125
+ next unless ancestor.const_defined?(:VERSION)
126
+ puts "#{ancestor}: #{ancestor::VERSION}"
127
+ end
128
+
129
+ puts "SQA: #{SQA::VERSION}" if SQA.const_defined?(:VERSION)
130
+
131
+ exit(0)
132
+ end
133
+
134
+ def update_config(params)
135
+ SQA.config.inject_additional_properties
136
+ my_hash = params.reject { |key, _| IGNORE_OPTIONS.include?(key) }
137
+ SQA.config.merge!(my_hash)
138
+ end
139
+ end
@@ -1,63 +1,103 @@
1
- # lib/sqa/command/web.rb
1
+ # sqa/lib/sqa/commands/web.rb
2
2
 
3
- # require 'tty-option'
3
+ class Commands::Web < Commands::Base
4
+ VERSION = "0.0.1-web"
4
5
 
6
+ Commands.register "web", self
5
7
 
6
- module SQA
7
- class Web < CLI
8
- include TTY::Option
8
+ desc "Start a web application"
9
+
10
+ option :image,
11
+ required: true,
12
+ type: :string,
13
+ desc: "The name of the image to use"
14
+
15
+ SQA::PluginManager.new_property(:restart, default: 'no', coerce: String)
16
+
17
+ option :restart,
18
+ aliases: %w[ --restart ],
19
+ type: :string,
20
+ default: "no",
21
+ values: %w[ no on-failure always unless-stopped ],
22
+ desc: "Restart policy to apply when a container exits"
23
+
24
+ SQA::PluginManager.new_property(:detach, default: 'no', coerce: String)
25
+
26
+ option :detach,
27
+ aliases: %w[ --detach ],
28
+ type: :boolean,
29
+ default: false,
30
+ desc: "Run container in background and print container ID"
31
+
32
+ SQA::PluginManager.new_property(:port, default: 4567, coerce: Integer)
9
33
 
10
- command "web"
34
+ option :port,
35
+ aliases: %w[ -p --port ],
36
+ type: :integer,
37
+ default: 4567,
38
+ desc: "The port where the web app will run"
11
39
 
12
- desc "Run a web server"
13
40
 
14
- example "Set working directory (-w)",
15
- " sqa web --port 4567 --data-dir /path/to/dir/ ubuntu pwd"
41
+ def initialize
42
+ # TODO: make it happen
43
+ end
44
+
16
45
 
17
- example <<~EOS
18
- Do Something
19
- sqa web
46
+ # params is Object from the ARGV parser
47
+ def call(params)
48
+ config = super
49
+
50
+ puts <<~EOS
51
+ ###############################
52
+ ## Running the Web Interface ##
53
+ ###############################
20
54
  EOS
55
+ end
56
+ end
21
57
 
22
- argument :image do
23
- required
24
- desc "The name of the image to use"
25
- end
26
58
 
27
- keyword :restart do
28
- default "no"
29
- permit %w[no on-failure always unless-stopped]
30
- desc "Restart policy to apply when a container exits"
31
- end
32
59
 
33
- flag :detach do
34
- long "--detach"
35
- desc "Run container in background and print container ID"
60
+ __END__
61
+
62
+ require 'sinatra/base'
63
+
64
+ module SQA
65
+ class Web < Sinatra::Base
66
+ set :port, SQA.config.port || 4567
67
+
68
+ get '/' do
69
+ "Welcome to SQA Web Interface!"
36
70
  end
37
71
 
38
- option :name do
39
- required
40
- long "--name string"
41
- desc "Assign a name to the container"
72
+
73
+ get '/stocks/:ticker' do
74
+ ticker = params[:ticker]
75
+ stock = SQA::Stock.new(ticker: ticker, source: :alpha_vantage)
76
+
77
+ "Stock: #{stock.data.name}, Ticker: #{stock.data.ticker}"
42
78
  end
43
79
 
44
- option :port do
45
- arity one_or_more
46
- long "--port integer"
47
- default 4567
48
- desc "The port where the web app will run"
80
+
81
+ get '/stocks/:ticker/indicators/:indicator' do
82
+ ticker = params[:ticker]
83
+ indicator = params[:indicator]
84
+ stock = SQA::Stock.new(ticker: ticker, source: :alpha_vantage)
85
+
86
+ indicator_value = SQA::Indicator.send(indicator, stock.df.adj_close_price, 14)
87
+
88
+ "Indicator #{indicator} for Stock #{ticker} is #{indicator_value}"
49
89
  end
50
90
 
91
+ # TODO: Add more routes as needed to expose more functionality
51
92
 
52
- def initialize
53
- # TODO: make it happen
54
- end
55
- end
93
+ # start the server if ruby file executed directly
94
+ run! if app_file == $0
95
+ end
56
96
  end
57
97
 
58
- __END__
59
98
 
60
99
 
100
+ ###################################################
61
101
  #!/usr/bin/env ruby
62
102
  # experiments/sinatra_examples/svg_viewer.rb
63
103
  # builds on md_viewer.rb
@@ -0,0 +1,22 @@
1
+ # sqa/lib/sqa/commands.rb
2
+
3
+ # Adds command options to SQA.config
4
+ require_relative "plugin_manager"
5
+
6
+ module SQA::Commands
7
+ # Establish the command registry
8
+ extend Dry::CLI::Registry
9
+ end
10
+
11
+ Commands = SQA::Commands
12
+
13
+
14
+ load_these_first = [
15
+ "#{__dir__}/commands/base.rb",
16
+ ].each { |file| require_relative file }
17
+
18
+ Dir.glob("#{__dir__}/commands/*.rb")
19
+ .reject{|file| load_these_first.include? file}
20
+ .each do |file|
21
+ require_relative file
22
+ end
data/lib/sqa/config.rb CHANGED
@@ -6,10 +6,18 @@
6
6
  # config file ..... overrides envar
7
7
  # command line parameters ...... overrides config file
8
8
 
9
+ require 'yaml'
10
+ require 'toml-rb'
9
11
 
10
12
  module SQA
13
+ # class Config < Hashie::Trash
14
+ # include Hashie::Extensions::IgnoreUndeclared
15
+ # include Hashie::Extensions::Coercion
16
+
17
+
11
18
  class Config < Hashie::Dash
12
19
  include Hashie::Extensions::Dash::PropertyTranslation
20
+ include Hashie::Extensions::MethodAccess
13
21
  include Hashie::Extensions::Coercion
14
22
 
15
23
  # FIXME: Getting undefined error PredefinedValues
@@ -19,7 +27,8 @@ module SQA
19
27
  #
20
28
  # include Hashie::Extensions::Dash::PredefinedValues
21
29
 
22
- property :config_file #,a String filepath for the current config overriden by cli options
30
+ property :command # a String currently, nil, analysis or web
31
+ property :config_file # a String filepath for the current config overriden by cli options
23
32
  property :dump_config # a String filepath into which to dump the current config
24
33
 
25
34
  property :data_dir, default: Nenv.home + "/sqa_data"
@@ -111,13 +120,10 @@ module SQA
111
120
  raise BadParameterError, "No config file given"
112
121
  end
113
122
 
114
- if File.exist?(config_file) &&
115
- File.file?(config_file) &&
116
- File.writable?(config_file)
117
- type = File.extname(config_file).downcase
118
- else
119
- type = "invalid"
120
- end
123
+ `touch #{config_file}`
124
+ # unless File.exist?(config_file)
125
+
126
+ type = File.extname(config_file).downcase
121
127
 
122
128
  if ".json" == type
123
129
  dump_json
@@ -129,7 +135,14 @@ module SQA
129
135
  dump_toml
130
136
 
131
137
  else
132
- raise BadParameterError, "Invalid Config File: #{config_file}"
138
+ raise BadParameterError, "Invalid Config File Type: #{config_file}"
139
+ end
140
+ end
141
+
142
+ # Method to dynamically extend properties from external sources (e.g., plugins)
143
+ def inject_additional_properties
144
+ SQA::PluginManager.registered_properties.each do |prop, options|
145
+ self.class.property(prop, options)
133
146
  end
134
147
  end
135
148
 
@@ -78,3 +78,4 @@ class SQA::DataFrame
78
78
  end
79
79
  end
80
80
  end
81
+
@@ -13,8 +13,6 @@ class SQA::Indicator; class << self
13
13
  true_ranges = true_range(high_prices, low_prices, close_prices)
14
14
  atr_values = []
15
15
 
16
- # debug_me{[ :period, :true_ranges ]}
17
-
18
16
  window_span = period - 1
19
17
 
20
18
  true_ranges.size.times do |inx|
@@ -25,14 +23,6 @@ class SQA::Indicator; class << self
25
23
 
26
24
  window = true_ranges[start_inx..end_inx]
27
25
 
28
- # debug_me{[
29
- # :inx,
30
- # :start_inx,
31
- # :end_inx,
32
- # :window,
33
- # "window.mean"
34
- # ]}
35
-
36
26
  atr_values << window.mean
37
27
  end
38
28
 
data/lib/sqa/init.rb CHANGED
@@ -23,7 +23,7 @@ module SQA
23
23
  # @@config = Config.new
24
24
 
25
25
  if defined? CLI
26
- CLI.run(argv)
26
+ CLI.run! # TODO: how to parse a fake argv? (argv)
27
27
  else
28
28
  # There are no real command line parameters
29
29
  # because the sqa gem is being required within
@@ -0,0 +1,20 @@
1
+ # lib/sqa/plugin_manager.rb
2
+
3
+ # This class gives plug-in commands a way
4
+ # to extend the SQA::Config class propertities.
5
+
6
+ module SQA
7
+ class PluginManager
8
+ @registered_properties = {}
9
+
10
+ class << self
11
+ attr_accessor :registered_properties
12
+
13
+ # name (Symbol)
14
+
15
+ def new_property(name, options = {})
16
+ @registered_properties[name.to_sym] = options
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/sqa/stock.rb CHANGED
@@ -16,6 +16,11 @@ class SQA::Stock
16
16
  attr_accessor :klass # class of historical and current prices
17
17
  attr_accessor :transformers # procs for changing column values from String to Numeric
18
18
 
19
+ # Holds the SQA::Strategy class name which seems to work
20
+ # the best for this stock.
21
+ attr_accessor :strategy # TODO: make part of the @data object
22
+
23
+
19
24
  def initialize(
20
25
  ticker:,
21
26
  source: :alpha_vantage
@@ -146,6 +151,30 @@ class SQA::Stock
146
151
  end
147
152
 
148
153
 
154
+ def associate_best_strategy(strategies)
155
+ best_strategy = nil
156
+ best_accuracy = 0
157
+
158
+ strategies.each do |strategy|
159
+ accuracy = evaluate_strategy(strategy)
160
+
161
+ if accuracy > best_accuracy
162
+ best_strategy = strategy
163
+ best_accuracy = accuracy
164
+ end
165
+ end
166
+
167
+ self.strategy = best_strategy
168
+ end
169
+
170
+
171
+ def evaluate_strategy(strategy)
172
+ # TODO: Implement this method to evaluate the accuracy of the strategy
173
+ # on the historical data of this stock.
174
+ end
175
+
176
+
177
+
149
178
  #############################################
150
179
  ## Class Methods
151
180