sqa 0.0.7 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +9 -0
- data/checksums/sqa-0.0.7.gem.sha512 +1 -0
- data/checksums/sqa-0.0.8.gem.sha512 +1 -0
- data/checksums/sqa-0.0.9.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 +179 -0
- data/lib/sqa/constants.rb +23 -0
- data/lib/sqa/data_frame.rb +1 -1
- data/lib/sqa/indicator/predict_next_value.rb +63 -0
- data/lib/sqa/strategy.rb +8 -0
- data/lib/sqa/version.rb +9 -2
- data/lib/sqa/web.rb +159 -0
- data/lib/sqa.rb +49 -49
- metadata +108 -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
|
+
option :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,179 @@
|
|
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 #,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
|
+
|
24
|
+
property :data_dir, default: Nenv.home + "/sqa_data"
|
25
|
+
|
26
|
+
# TODO: If no path is given, these files will be in
|
27
|
+
# data directory, otherwise, use the given path
|
28
|
+
property :portfolio_filename, from: :portfolio, default: "portfolio.csv"
|
29
|
+
property :trades_filename, from: :trades, default: "trades.csv"
|
30
|
+
|
31
|
+
property :log_level, default: :info, coerce: Symbol, values: %i[debug info warn error fatal]
|
32
|
+
|
33
|
+
# TODO: need a custom proc since there is no Boolean class in Ruby
|
34
|
+
property :debug, default: false #, coerce: Boolean
|
35
|
+
property :verbose, default: false #, coerce: Boolean
|
36
|
+
|
37
|
+
# TODO: use svggraph
|
38
|
+
property :plotting_library, from: :plot_lib, default: :gruff, coerce: Symbol
|
39
|
+
property :lazy_update, from: :lazy, default: false
|
40
|
+
|
41
|
+
|
42
|
+
coerce_key :debug, ->(v) do
|
43
|
+
case v
|
44
|
+
when String
|
45
|
+
!!(v =~ /\A(true|t|yes|y|1)\z/i)
|
46
|
+
when Numeric
|
47
|
+
!v.to_i.zero?
|
48
|
+
else
|
49
|
+
v == true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
coerce_key :verbose, ->(v) do
|
54
|
+
case v
|
55
|
+
when String
|
56
|
+
!!(v =~ /\A(true|t|yes|y|1)\z/i)
|
57
|
+
when Numeric
|
58
|
+
!v.to_i.zero?
|
59
|
+
else
|
60
|
+
v == true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
########################################################
|
65
|
+
def initialize(a_hash={})
|
66
|
+
super(a_hash)
|
67
|
+
override_with_envars
|
68
|
+
end
|
69
|
+
|
70
|
+
def debug? = debug
|
71
|
+
def verbose? = verbose
|
72
|
+
|
73
|
+
|
74
|
+
########################################################
|
75
|
+
def from_file
|
76
|
+
return if config_file.nil?
|
77
|
+
|
78
|
+
if File.exist?(config_file) &&
|
79
|
+
File.file?(config_file) &&
|
80
|
+
File.readable?(config_file)
|
81
|
+
type = File.extname(config_file).downcase
|
82
|
+
else
|
83
|
+
type = "invalid"
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO: arrange order in mostly often used
|
87
|
+
|
88
|
+
if ".json" == type
|
89
|
+
form_json
|
90
|
+
|
91
|
+
elsif %w[.yml .yaml].include?(type)
|
92
|
+
from_yaml
|
93
|
+
|
94
|
+
elsif ".toml" == type
|
95
|
+
from_toml
|
96
|
+
|
97
|
+
else
|
98
|
+
raise BadParameterError, "Invalid Config File: #{config_file}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def dump_file
|
103
|
+
if config_file.nil?
|
104
|
+
raise BadParameterError, "No config file given"
|
105
|
+
end
|
106
|
+
|
107
|
+
if File.exist?(config_file) &&
|
108
|
+
File.file?(config_file) &&
|
109
|
+
File.writable?(config_file)
|
110
|
+
type = File.extname(config_file).downcase
|
111
|
+
else
|
112
|
+
type = "invalid"
|
113
|
+
end
|
114
|
+
|
115
|
+
if ".json" == type
|
116
|
+
dump_json
|
117
|
+
|
118
|
+
elsif %w[.yml .yaml].include?(type)
|
119
|
+
dump_yaml
|
120
|
+
|
121
|
+
elsif ".toml" == type
|
122
|
+
dump_toml
|
123
|
+
|
124
|
+
else
|
125
|
+
raise BadParameterError, "Invalid Config File: #{config_file}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
########################################################
|
131
|
+
private
|
132
|
+
|
133
|
+
def override_with_envars(prefix = "SQA_")
|
134
|
+
keys.each do |key|
|
135
|
+
envar = ENV["#{prefix}#{key.to_s.upcase}"]
|
136
|
+
send("#{key}=", envar) unless envar.nil?
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
#####################################
|
142
|
+
## override values from a config file
|
143
|
+
|
144
|
+
def from_json
|
145
|
+
incoming = ::JSON.load(File.open(config_file).read)
|
146
|
+
debug_me{[ :incoming ]}
|
147
|
+
end
|
148
|
+
|
149
|
+
def from_toml
|
150
|
+
incoming = TomlRB.load_file(config_file)
|
151
|
+
debug_me{[ :incoming ]}
|
152
|
+
end
|
153
|
+
|
154
|
+
def from_yaml
|
155
|
+
incoming = ::YAML.load_file(config_file)
|
156
|
+
debug_me{[ :incoming ]}
|
157
|
+
merge! incoming
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
#####################################
|
162
|
+
## dump values to a config file
|
163
|
+
|
164
|
+
def as_hash = to_h.reject{|k, _| :config_file == k}
|
165
|
+
def dump_json = File.open(config_file, "w") { |f| f.write JSON.pretty_generate(as_hash)}
|
166
|
+
def dump_toml = File.open(config_file, "w") { |f| f.write TomlRB.dump(as_hash)}
|
167
|
+
def dump_yaml = File.open(config_file, "w") { |f| f.write as_hash.to_yaml}
|
168
|
+
|
169
|
+
|
170
|
+
#####################################
|
171
|
+
class << self
|
172
|
+
def reset
|
173
|
+
SQA.config = new
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
SQA::Config.reset
|
@@ -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
|
data/lib/sqa/data_frame.rb
CHANGED