sqa 0.0.7 → 0.0.8
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.7.gem.sha512 +1 -0
- data/checksums/sqa-0.0.8.gem.sha512 +1 -0
- data/docs/.gitignore +1 -0
- data/docs/predict_next_value.md +15 -0
- data/lib/patches/daru/category.rb +19 -0
- data/lib/patches/daru/data_frame.rb +19 -0
- data/lib/patches/daru/plotting/svg-graph/category.rb +55 -0
- data/lib/patches/daru/plotting/svg-graph/dataframe.rb +105 -0
- data/lib/patches/daru/plotting/svg-graph/vector.rb +102 -0
- data/lib/patches/daru/plotting/svg-graph.rb +7 -0
- data/lib/patches/daru/vector.rb +19 -0
- data/lib/patches/daru.rb +19 -0
- data/lib/sqa/analysis.rb +306 -0
- data/lib/sqa/cli.rb +145 -296
- data/lib/sqa/config.rb +169 -0
- data/lib/sqa/constants.rb +23 -0
- data/lib/sqa/indicator/predict_next_value.rb +63 -0
- data/lib/sqa/version.rb +9 -2
- data/lib/sqa/web.rb +159 -0
- data/lib/sqa.rb +43 -49
- metadata +107 -6
data/lib/sqa/cli.rb
CHANGED
@@ -1,324 +1,173 @@
|
|
1
1
|
# lib/sqa/cli.rb
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
module SQA
|
6
|
-
class CLI
|
7
|
-
include Mixlib::CLI
|
8
|
-
|
9
|
-
option :config_file,
|
10
|
-
short: "-c CONFIG",
|
11
|
-
long: "--config CONFIG",
|
12
|
-
default: "~/.sqa.rb",
|
13
|
-
description: "The configuration file to use"
|
14
|
-
|
15
|
-
option :log_level,
|
16
|
-
short: "-l LEVEL",
|
17
|
-
long: "--log_level LEVEL",
|
18
|
-
description: "Set the log level (debug, info, warn, error, fatal)",
|
19
|
-
required: true,
|
20
|
-
in: [:debug, :info, :warn, :error, :fatal],
|
21
|
-
proc: Proc.new { |l| l.to_sym }
|
22
|
-
|
23
|
-
option :help,
|
24
|
-
short: "-h",
|
25
|
-
long: "--help",
|
26
|
-
description: "Show this message",
|
27
|
-
on: :tail,
|
28
|
-
boolean: true,
|
29
|
-
show_options: true,
|
30
|
-
exit: 0
|
31
|
-
|
32
|
-
def run(argv = ARGV)
|
33
|
-
parse_options(argv)
|
34
|
-
SQA::Config.merge!(config)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
__END__
|
40
|
-
|
41
|
-
###################################################
|
42
|
-
## This is the old thing that got me started ...
|
43
|
-
|
44
|
-
#!/usr/bin/env ruby
|
45
|
-
# experiments/stocks/analysis.rb
|
46
|
-
#
|
47
|
-
# Some technical indicators from FinTech gem
|
48
|
-
#
|
49
|
-
# optional date CLI option in format YYYY-mm-dd
|
50
|
-
# if not present uses Date.today
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
INVEST = 1000.00
|
55
|
-
|
56
|
-
require 'pathname'
|
57
|
-
|
58
|
-
require_relative 'stock'
|
59
|
-
require_relative 'datastore'
|
60
|
-
|
61
|
-
|
62
|
-
STOCKS = Pathname.pwd + "stocks.txt"
|
63
|
-
TRADES = Pathname.pwd + "trades.csv"
|
64
|
-
|
65
|
-
TRADES_FILE = File.open(TRADES, 'a')
|
66
|
-
|
67
|
-
unless STOCKS.exist?
|
68
|
-
puts
|
69
|
-
puts "ERROR: The 'stocks.txt' file does not exist."
|
70
|
-
puts
|
71
|
-
exot(-1)
|
72
|
-
end
|
73
|
-
|
74
|
-
require 'debug_me'
|
75
|
-
include DebugMe
|
76
|
-
|
77
|
-
require 'csv'
|
78
|
-
require 'date'
|
79
|
-
require 'tty-table'
|
80
|
-
|
81
|
-
require 'fin_tech'
|
82
|
-
require 'previous_dow'
|
83
|
-
|
84
|
-
class NilClass
|
85
|
-
def blank?() = true
|
86
|
-
end
|
87
|
-
|
88
|
-
class String
|
89
|
-
def blank?() = strip().empty?
|
90
|
-
end
|
91
|
-
|
92
|
-
class Array
|
93
|
-
def blank?() = empty?
|
94
|
-
end
|
95
|
-
|
3
|
+
require 'tty-option'
|
96
4
|
|
97
|
-
|
98
|
-
return @tickers unless @tickers.blank?
|
5
|
+
require_relative '../sqa'
|
99
6
|
|
100
|
-
|
7
|
+
# SMELL: Architectyre has become confused between CLI and Command
|
101
8
|
|
102
|
-
|
103
|
-
ticker_symbol = a_line.chomp.strip.split()&.first&.downcase
|
104
|
-
next if ticker_symbol.blank? || '#' == ticker_symbol
|
105
|
-
@tickers << ticker_symbol unless @tickers.include?(ticker_symbol)
|
106
|
-
end
|
9
|
+
# TODO: Fix the mess between CLI and Command
|
107
10
|
|
108
|
-
@tickers.sort!
|
109
|
-
end
|
110
11
|
|
111
|
-
|
12
|
+
module SQA
|
13
|
+
class CLI
|
14
|
+
include TTY::Option
|
112
15
|
|
113
|
-
|
114
|
-
|
16
|
+
header "Stock Quantitative Analysis (SQA)"
|
17
|
+
footer "WARNING: This is a toy, a play thing, not intended for serious use."
|
115
18
|
|
116
|
-
|
19
|
+
program "sqa"
|
20
|
+
desc "A collection of things"
|
117
21
|
|
22
|
+
example "sqa -c ~/.sqa.yml -p portfolio.csv -t trades.csv --data-dir ~/sqa_data"
|
118
23
|
|
119
|
-
#######################################################################
|
120
|
-
# download a CSV file from https://query1.finance.yahoo.com
|
121
|
-
# given a stock ticker symbol as a String
|
122
|
-
# start and end dates
|
123
|
-
#
|
124
|
-
# For ticker "aapl" the downloaded file will be named "aapl.csv"
|
125
|
-
# That filename will be renamed to "aapl_YYYYmmdd.csv" where the
|
126
|
-
# date suffix is the end_date of the historical data.
|
127
|
-
#
|
128
|
-
def download_historical_prices(ticker, start_date, end_date)
|
129
|
-
data_path = Pathname.pwd + "#{ticker}_#{ASOF}.csv"
|
130
|
-
return if data_path.exist?
|
131
24
|
|
132
|
-
|
25
|
+
option :config_file do
|
26
|
+
short "-c string"
|
27
|
+
long "--config string"
|
28
|
+
desc "Path to the config file"
|
29
|
+
end
|
133
30
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
31
|
+
option :log_level do
|
32
|
+
short "-l string"
|
33
|
+
long "--log_level string"
|
34
|
+
# default SQA.config.log_level
|
35
|
+
desc "Set the log level (debug, info, warn, error, fatal)"
|
36
|
+
end
|
138
37
|
|
139
|
-
|
38
|
+
option :portfolio do
|
39
|
+
short "-p string"
|
40
|
+
long "--portfolio string"
|
41
|
+
# default SQA.config.portfolio_filename
|
42
|
+
desc "Set the filename of the portfolio"
|
43
|
+
end
|
140
44
|
|
141
|
-
mew_path.rename data_path
|
142
|
-
end
|
143
45
|
|
46
|
+
option :trades do
|
47
|
+
short "-t string"
|
48
|
+
long "--trades string"
|
49
|
+
# default SQA.config.trades_filename
|
50
|
+
desc "Set the filename into which trades are stored"
|
51
|
+
end
|
144
52
|
|
145
|
-
#######################################################################
|
146
|
-
# Read the CSV file associated with the give ticker symbol
|
147
|
-
# and the ASOF date.
|
148
|
-
#
|
149
|
-
def read_csv(ticker)
|
150
|
-
filename = "#{ticker.downcase}_#{ASOF}.csv"
|
151
|
-
data = []
|
152
53
|
|
153
|
-
|
154
|
-
data
|
155
|
-
|
54
|
+
option :data_dir do
|
55
|
+
long "--data-dir string"
|
56
|
+
# default SQA.config.data_dir
|
57
|
+
desc "Set the directory for the SQA data"
|
58
|
+
end
|
156
59
|
|
157
|
-
|
158
|
-
|
60
|
+
flag :dump_config do
|
61
|
+
long "--dump-config path_to_file"
|
62
|
+
desc "Dump the current configuration"
|
63
|
+
end
|
159
64
|
|
160
|
-
|
161
|
-
|
65
|
+
flag :help do
|
66
|
+
short "-h"
|
67
|
+
long "--help"
|
68
|
+
desc "Print usage"
|
69
|
+
end
|
162
70
|
|
163
|
-
|
164
|
-
|
165
|
-
|
71
|
+
flag :version do
|
72
|
+
long "--version"
|
73
|
+
desc "Print version"
|
74
|
+
end
|
166
75
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
#
|
76
|
+
flag :debug do
|
77
|
+
short "-d"
|
78
|
+
long "--debug"
|
79
|
+
# default SQA.config.debug
|
80
|
+
desc "Turn on debugging output"
|
81
|
+
end
|
171
82
|
|
83
|
+
flag :verbose do
|
84
|
+
short "-v"
|
85
|
+
long "--verbose"
|
86
|
+
# default SQA.config.debug
|
87
|
+
desc "Print verbosely"
|
88
|
+
end
|
172
89
|
|
173
|
-
|
174
|
-
|
90
|
+
class << self
|
91
|
+
@@subclasses = []
|
92
|
+
@@commands_available = []
|
93
|
+
|
94
|
+
def names
|
95
|
+
'['+ @@commands_available.join('|')+']'
|
96
|
+
end
|
97
|
+
|
98
|
+
def inherited(subclass)
|
99
|
+
super
|
100
|
+
@@subclasses << subclass
|
101
|
+
@@commands_available << subclass.command.join
|
102
|
+
end
|
103
|
+
|
104
|
+
def command_descriptions
|
105
|
+
help_block = "Optional Command Available:"
|
106
|
+
|
107
|
+
@@commands_available.size.times do |x|
|
108
|
+
klass = @@subclasses[x]
|
109
|
+
help_block << "\n " + @@commands_available[x] + " - "
|
110
|
+
help_block << klass.desc.join
|
111
|
+
end
|
112
|
+
|
113
|
+
help_block
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
##################################################
|
118
|
+
def run(argv = ARGV)
|
119
|
+
cli = new
|
120
|
+
parser = cli.parse(argv)
|
121
|
+
params = parser.params
|
122
|
+
|
123
|
+
if params[:help]
|
124
|
+
print parser.help
|
125
|
+
exit(0)
|
126
|
+
|
127
|
+
elsif params.errors.any?
|
128
|
+
puts params.errors.summary
|
129
|
+
exit(1)
|
130
|
+
|
131
|
+
elsif params[:version]
|
132
|
+
puts SQA.version
|
133
|
+
exit(0)
|
134
|
+
|
135
|
+
elsif params[:dump_config]
|
136
|
+
SQA.config.config_file = params[:dump_config]
|
137
|
+
`touch #{SQA.config.config_file}`
|
138
|
+
SQA.config.dump_file
|
139
|
+
exit(0)
|
140
|
+
|
141
|
+
elsif params[:config_file]
|
142
|
+
# Override the defaults <- envars <- config file content
|
143
|
+
SQA.config.config_file = params[:config_file]
|
144
|
+
SQA.config.from_file
|
145
|
+
end
|
146
|
+
|
147
|
+
# Override the defaults <- envars <- config file <- cli parameters
|
148
|
+
SQA.config.merge!(remove_temps params.to_h)
|
149
|
+
|
150
|
+
if SQA.debug? || SQA.verbose?
|
151
|
+
debug_me("config after CLI parameters"){[
|
152
|
+
"SQA.config"
|
153
|
+
]}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
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
|
+
end
|
162
|
+
end
|
163
|
+
end
|
175
164
|
end
|
176
165
|
|
177
|
-
|
178
|
-
|
179
|
-
mwfd = 14 # moving_window_forcast_days
|
180
|
-
|
181
|
-
headers = %w[ Ticker AdjClose Trend Slope M'tum RSI Analysis MACD Target Signal $]
|
182
|
-
values = []
|
183
|
-
|
184
|
-
tickers.each do |ticker|
|
185
|
-
|
186
|
-
data = read_csv ticker
|
187
|
-
prices = data.map{|r| r["Adj Close"].to_f}
|
188
|
-
volumes = data.map{|r| r["volume"].to_f}
|
189
|
-
|
190
|
-
if data.blank?
|
191
|
-
puts
|
192
|
-
puts "ERROR: cannot get data for #{ticker}"
|
193
|
-
puts
|
194
|
-
next
|
195
|
-
end
|
196
|
-
|
197
|
-
|
198
|
-
result[ticker] = {
|
199
|
-
date: data.last["Date"],
|
200
|
-
adj_close: data.last["Adj Close"].to_f
|
201
|
-
}
|
202
|
-
|
203
|
-
result[ticker][:market] = FinTech.classify_market_profile(
|
204
|
-
volumes.last(mwfd),
|
205
|
-
prices.last(mwfd),
|
206
|
-
prices.last(mwfd).first,
|
207
|
-
prices.last
|
208
|
-
)
|
209
|
-
|
210
|
-
fr = FinTech.fibonacci_retracement( prices.last(mwfd).first,
|
211
|
-
prices.last).map{|x| x.round(3)}
|
212
|
-
|
213
|
-
|
214
|
-
puts "\n#{result[ticker][:market]} .. #{ticker}\t#{fr}"
|
215
|
-
print "\t"
|
216
|
-
print FinTech.head_and_shoulders_pattern?(prices.last(mwfd))
|
217
|
-
print "\t"
|
218
|
-
print FinTech.double_top_bottom_pattern?(prices.last(mwfd))
|
219
|
-
print "\t"
|
220
|
-
mr = FinTech.mean_reversion?(prices, mwfd, 0.5)
|
221
|
-
print mr
|
222
|
-
|
223
|
-
if mr
|
224
|
-
print "\t"
|
225
|
-
print FinTech.mr_mean(prices, mwfd).round(3)
|
226
|
-
end
|
227
|
-
|
228
|
-
print "\t"
|
229
|
-
print FinTech.identify_wave_condition?(prices, 2*mwfd, 1.0)
|
230
|
-
puts
|
231
|
-
|
232
|
-
print "\t"
|
233
|
-
print FinTech.ema_analysis(prices, mwfd).except(:ema_values)
|
234
|
-
|
235
|
-
puts
|
236
|
-
|
237
|
-
row = [ ticker ]
|
238
|
-
|
239
|
-
# result[ticker][:moving_averages] = FinTech.sma(data, mwfd)
|
240
|
-
result[ticker][:trend] = FinTech.sma_trend(data, mwfd)
|
241
|
-
result[ticker][:momentum] = FinTech.momentum(prices, mwfd)
|
242
|
-
result[ticker][:rsi] = FinTech.rsi(data, mwfd)
|
243
|
-
result[ticker][:bollinger_bands] = FinTech.bollinger_bands(data, mwfd, 2)
|
244
|
-
result[ticker][:macd] = FinTech.macd(data, mwfd, 2*mwfd, mwfd/2)
|
245
|
-
|
246
|
-
price = result[ticker][:adj_close].round(3)
|
247
|
-
|
248
|
-
row << price
|
249
|
-
row << result[ticker][:trend][:trend]
|
250
|
-
row << result[ticker][:trend][:angle].round(3)
|
251
|
-
row << result[ticker][:momentum].round(3)
|
252
|
-
row << result[ticker][:rsi][:rsi].round(3)
|
253
|
-
row << result[ticker][:rsi][:meaning]
|
254
|
-
row << result[ticker][:macd].first.round(3)
|
255
|
-
row << result[ticker][:macd].last.round(3)
|
256
|
-
|
257
|
-
analysis = result[ticker][:rsi][:meaning]
|
258
|
-
|
259
|
-
signal = ""
|
260
|
-
macd_diff = result[ticker][:macd].first
|
261
|
-
target = result[ticker][:macd].last
|
262
|
-
current = result[ticker][:adj_close]
|
263
|
-
|
264
|
-
trend_down = "down" == result[ticker][:trend][:trend]
|
265
|
-
|
266
|
-
if current < target
|
267
|
-
signal = "buy" unless "Over Bought" == analysis
|
268
|
-
elsif (current > target) && trend_down
|
269
|
-
signal = "sell" unless "Over Sold" == analysis
|
270
|
-
end
|
271
|
-
|
272
|
-
if "buy" == signal
|
273
|
-
pps = target - price
|
274
|
-
shares = INVEST.to_i / price.to_i
|
275
|
-
upside = (shares * pps).round(2)
|
276
|
-
trade(ticker, signal, shares, price)
|
277
|
-
elsif "sell" == signal
|
278
|
-
pps = target - price
|
279
|
-
shares = INVEST.to_i / price.to_i
|
280
|
-
upside = (shares * pps).round(2)
|
281
|
-
trade(ticker, signal, shares, price)
|
282
|
-
else
|
283
|
-
upside = ""
|
284
|
-
end
|
285
|
-
|
286
|
-
row << signal
|
287
|
-
row << upside
|
288
|
-
|
289
|
-
values << row
|
290
|
-
end
|
166
|
+
require_relative 'analysis'
|
167
|
+
require_relative 'web'
|
291
168
|
|
292
|
-
#
|
293
|
-
#
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
the_table = TTY::Table.new(headers, values)
|
298
|
-
|
299
|
-
puts
|
300
|
-
puts "Analysis as of Friday Close: #{end_date}"
|
301
|
-
|
302
|
-
puts the_table.render(
|
303
|
-
:unicode,
|
304
|
-
{
|
305
|
-
padding: [0, 0, 0, 0],
|
306
|
-
alignments: [
|
307
|
-
:left, # ticker
|
308
|
-
:right, # adj close
|
309
|
-
:center, # trend
|
310
|
-
:right, # slope
|
311
|
-
:right, # momentum
|
312
|
-
:right, # rsi
|
313
|
-
:center, # meaning / analysis
|
314
|
-
:right, # macd
|
315
|
-
:right, # target
|
316
|
-
:center, # signal
|
317
|
-
:right # upside
|
318
|
-
],
|
319
|
-
}
|
320
|
-
)
|
321
|
-
puts
|
322
|
-
|
323
|
-
TRADES_FILE.close
|
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
|
324
173
|
|
data/lib/sqa/config.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
# lib/sqa/config.rb
|
2
|
+
|
3
|
+
# The hierarchies of values should be:
|
4
|
+
# default
|
5
|
+
# envar ..... overrides default
|
6
|
+
# config file ..... overrides envar
|
7
|
+
# command line parameters ...... overrides config file
|
8
|
+
|
9
|
+
require 'hashie'
|
10
|
+
require 'yaml'
|
11
|
+
require 'json'
|
12
|
+
require 'toml-rb'
|
13
|
+
|
14
|
+
|
15
|
+
module SQA
|
16
|
+
class Config < Hashie::Dash
|
17
|
+
include Hashie::Extensions::Dash::PropertyTranslation
|
18
|
+
include Hashie::Extensions::Coercion
|
19
|
+
include Hashie::Extensions::Dash::PredefinedValues
|
20
|
+
|
21
|
+
property :config_file #, default: Nenv.home + "/.sqa.yml"
|
22
|
+
property :data_dir, default: Nenv.home + "/sqa_data"
|
23
|
+
|
24
|
+
# TODO: If no path is given, these files will be in
|
25
|
+
# data directory, otherwise, use the given path
|
26
|
+
property :portfolio_filename, from: :portfolio, default: "portfolio.csv"
|
27
|
+
property :trades_filename, from: :trades, default: "trades.csv"
|
28
|
+
|
29
|
+
property :log_level, default: :info, coerce: Symbol, values: %i[debug info warn error fatal]
|
30
|
+
|
31
|
+
# TODO: need a custom proc since there is no Boolean class in Ruby
|
32
|
+
property :debug, default: false #, coerce: Boolean
|
33
|
+
property :verbose, default: false #, coerce: Boolean
|
34
|
+
|
35
|
+
# TODO: use svggraph
|
36
|
+
property :plotting_library, from: :plot_lib, default: :gruff, coerce: Symbol
|
37
|
+
property :lazy_update, from: :lazy, default: false
|
38
|
+
|
39
|
+
|
40
|
+
coerce_key :debug, ->(v) do
|
41
|
+
case v
|
42
|
+
when String
|
43
|
+
!!(v =~ /\A(true|t|yes|y|1)\z/i)
|
44
|
+
when Numeric
|
45
|
+
!v.to_i.zero?
|
46
|
+
else
|
47
|
+
v == true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
coerce_key :verbose, ->(v) do
|
52
|
+
case v
|
53
|
+
when String
|
54
|
+
!!(v =~ /\A(true|t|yes|y|1)\z/i)
|
55
|
+
when Numeric
|
56
|
+
!v.to_i.zero?
|
57
|
+
else
|
58
|
+
v == true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
########################################################
|
63
|
+
def initialize(a_hash={})
|
64
|
+
super(a_hash)
|
65
|
+
override_with_envars
|
66
|
+
end
|
67
|
+
|
68
|
+
def debug? = debug
|
69
|
+
def verbose? = verbose
|
70
|
+
|
71
|
+
|
72
|
+
########################################################
|
73
|
+
def from_file
|
74
|
+
return if config_file.nil?
|
75
|
+
|
76
|
+
if File.exist?(config_file) &&
|
77
|
+
File.file?(config_file) &&
|
78
|
+
File.readable?(config_file)
|
79
|
+
type = File.extname(config_file).downcase
|
80
|
+
else
|
81
|
+
type = "invalid"
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: arrange order in mostly often used
|
85
|
+
|
86
|
+
if ".json" == type
|
87
|
+
form_json
|
88
|
+
|
89
|
+
elsif %w[.yml .yaml].include?(type)
|
90
|
+
from_yaml
|
91
|
+
|
92
|
+
elsif ".toml" == type
|
93
|
+
from_toml
|
94
|
+
|
95
|
+
else
|
96
|
+
raise BadParameterError, "Invalid Config File: #{config_file}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def dump_file
|
101
|
+
if config_file.nil?
|
102
|
+
raise BadParameterError, "No config file given"
|
103
|
+
end
|
104
|
+
|
105
|
+
if File.exist?(config_file) &&
|
106
|
+
File.file?(config_file) &&
|
107
|
+
File.writable?(config_file)
|
108
|
+
type = File.extname(config_file).downcase
|
109
|
+
else
|
110
|
+
type = "invalid"
|
111
|
+
end
|
112
|
+
|
113
|
+
if ".json" == type
|
114
|
+
dump_json
|
115
|
+
|
116
|
+
elsif %w[.yml .yaml].include?(type)
|
117
|
+
dump_yaml
|
118
|
+
|
119
|
+
elsif ".toml" == type
|
120
|
+
dump_toml
|
121
|
+
|
122
|
+
else
|
123
|
+
raise BadParameterError, "Invalid Config File: #{config_file}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
########################################################
|
129
|
+
private
|
130
|
+
|
131
|
+
def override_with_envars(prefix = "SQA_")
|
132
|
+
keys.each do |key|
|
133
|
+
envar = ENV["#{prefix}#{key.to_s.upcase}"]
|
134
|
+
send("#{key}=", envar) unless envar.nil?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
#####################################
|
140
|
+
## override values from a config file
|
141
|
+
|
142
|
+
def from_json
|
143
|
+
incoming = ::JSON.load(File.open(config_file).read)
|
144
|
+
debug_me{[ :incoming ]}
|
145
|
+
end
|
146
|
+
|
147
|
+
def from_toml
|
148
|
+
incoming = TomlRB.load_file(config_file)
|
149
|
+
debug_me{[ :incoming ]}
|
150
|
+
end
|
151
|
+
|
152
|
+
def from_yaml
|
153
|
+
incoming = ::YAML.load_file(config_file)
|
154
|
+
debug_me{[ :incoming ]}
|
155
|
+
merge! incoming
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
#####################################
|
160
|
+
## dump values to a config file
|
161
|
+
|
162
|
+
def as_hash = to_h.reject{|k, _| :config_file == k}
|
163
|
+
def dump_json = File.open(config_file, "w") { |f| f.write JSON.pretty_generate(as_hash)}
|
164
|
+
def dump_toml = File.open(config_file, "w") { |f| f.write TomlRB.dump(as_hash)}
|
165
|
+
def dump_yaml = File.open(config_file, "w") { |f| f.write as_hash.to_yaml}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
SQA.config = SQA::Config.new
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# lib/sqa/constants.rb
|
2
|
+
|
3
|
+
module SQA
|
4
|
+
module Constants
|
5
|
+
Signal = {
|
6
|
+
hold: 0,
|
7
|
+
buy: 1,
|
8
|
+
sell: 2
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
Trend = {
|
12
|
+
up: 0,
|
13
|
+
down: 1
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
Swing = {
|
17
|
+
valley: 0,
|
18
|
+
peak: 1,
|
19
|
+
}.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
include Constants
|
23
|
+
end
|