sqa 0.0.22 → 0.0.31
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/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +86 -0
- data/CLAUDE.md +653 -0
- data/COMMITS.md +196 -0
- data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
- data/NEXT-STEPS.md +154 -0
- data/README.md +833 -213
- data/TASKS.md +358 -0
- data/TEST_RESULTS.md +140 -0
- data/TODO.md +42 -0
- data/_notes.txt +25 -0
- data/bin/sqa-console +11 -0
- data/checksums/sqa-0.0.23.gem.sha512 +1 -0
- data/checksums/sqa-0.0.24.gem.sha512 +1 -0
- data/data/talk_talk.json +103284 -0
- data/develop_summary.md +313 -0
- data/docs/advanced/backtesting.md +206 -0
- data/docs/advanced/ensemble.md +68 -0
- data/docs/advanced/fpop.md +153 -0
- data/docs/advanced/index.md +112 -0
- data/docs/advanced/multi-timeframe.md +67 -0
- data/docs/advanced/pattern-matcher.md +75 -0
- data/docs/advanced/portfolio-optimizer.md +79 -0
- data/docs/advanced/portfolio.md +166 -0
- data/docs/advanced/risk-management.md +210 -0
- data/docs/advanced/strategy-generator.md +158 -0
- data/docs/advanced/streaming.md +209 -0
- data/docs/ai_and_ml.md +80 -0
- data/docs/api/dataframe.md +1115 -0
- data/docs/api/index.md +126 -0
- data/docs/assets/css/custom.css +88 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/concepts/index.md +68 -0
- data/docs/contributing/index.md +60 -0
- data/docs/data-sources/index.md +66 -0
- data/docs/data_frame.md +317 -97
- data/docs/factors_that_impact_price.md +26 -0
- data/docs/finviz.md +11 -0
- data/docs/fx_pro_bit.md +25 -0
- data/docs/genetic_programming.md +104 -0
- data/docs/getting-started/index.md +123 -0
- data/docs/getting-started/installation.md +229 -0
- data/docs/getting-started/quick-start.md +244 -0
- data/docs/i_gotta_an_idea.md +22 -0
- data/docs/index.md +163 -0
- data/docs/indicators/index.md +97 -0
- data/docs/indicators.md +110 -24
- data/docs/options.md +8 -0
- data/docs/strategies/bollinger-bands.md +146 -0
- data/docs/strategies/consensus.md +64 -0
- data/docs/strategies/custom.md +310 -0
- data/docs/strategies/ema.md +53 -0
- data/docs/strategies/index.md +92 -0
- data/docs/strategies/kbs.md +164 -0
- data/docs/strategies/macd.md +96 -0
- data/docs/strategies/market-profile.md +54 -0
- data/docs/strategies/mean-reversion.md +58 -0
- data/docs/strategies/rsi.md +95 -0
- data/docs/strategies/sma.md +55 -0
- data/docs/strategies/stochastic.md +63 -0
- data/docs/strategies/volume-breakout.md +54 -0
- data/docs/ta_lib.md +160 -0
- data/docs/tags.md +7 -0
- data/docs/true_strength_index.md +46 -0
- data/docs/weighted_moving_average.md +48 -0
- data/examples/README.md +354 -0
- data/examples/advanced_features_example.rb +350 -0
- data/examples/fpop_analysis_example.rb +191 -0
- data/examples/genetic_programming_example.rb +148 -0
- data/examples/kbs_strategy_example.rb +208 -0
- data/examples/pattern_context_example.rb +300 -0
- data/examples/rails_app/Gemfile +34 -0
- data/examples/rails_app/README.md +416 -0
- data/examples/rails_app/app/assets/javascripts/application.js +107 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
- data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
- data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
- data/examples/rails_app/app/controllers/application_controller.rb +22 -0
- data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
- data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
- data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
- data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
- data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
- data/examples/rails_app/app/views/errors/show.html.erb +17 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
- data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
- data/examples/rails_app/bin/rails +6 -0
- data/examples/rails_app/config/application.rb +45 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +18 -0
- data/examples/rails_app/config/environment.rb +11 -0
- data/examples/rails_app/config/routes.rb +26 -0
- data/examples/rails_app/config.ru +8 -0
- data/examples/realtime_stream_example.rb +274 -0
- data/examples/sinatra_app/Gemfile +22 -0
- data/examples/sinatra_app/QUICKSTART.md +159 -0
- data/examples/sinatra_app/README.md +461 -0
- data/examples/sinatra_app/app.rb +344 -0
- data/examples/sinatra_app/config.ru +5 -0
- data/examples/sinatra_app/public/css/style.css +659 -0
- data/examples/sinatra_app/public/js/app.js +107 -0
- data/examples/sinatra_app/views/analyze.erb +306 -0
- data/examples/sinatra_app/views/backtest.erb +325 -0
- data/examples/sinatra_app/views/dashboard.erb +419 -0
- data/examples/sinatra_app/views/error.erb +58 -0
- data/examples/sinatra_app/views/index.erb +118 -0
- data/examples/sinatra_app/views/layout.erb +61 -0
- data/examples/sinatra_app/views/portfolio.erb +43 -0
- data/examples/strategy_generator_example.rb +346 -0
- data/hsa_portfolio.csv +11 -0
- data/justfile +0 -0
- data/lib/api/alpha_vantage_api.rb +462 -0
- data/lib/sqa/backtest.rb +329 -0
- data/lib/sqa/config.rb +22 -9
- data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +34 -41
- data/lib/sqa/data_frame.rb +148 -243
- data/lib/sqa/ensemble.rb +359 -0
- data/lib/sqa/fpop.rb +199 -0
- data/lib/sqa/gp.rb +259 -0
- data/lib/sqa/indicator.rb +5 -8
- data/lib/sqa/init.rb +16 -9
- data/lib/sqa/market_regime.rb +240 -0
- data/lib/sqa/multi_timeframe.rb +379 -0
- data/lib/sqa/pattern_matcher.rb +497 -0
- data/lib/sqa/plugin_manager.rb +20 -0
- data/lib/sqa/portfolio.rb +260 -6
- data/lib/sqa/portfolio_optimizer.rb +377 -0
- data/lib/sqa/risk_manager.rb +442 -0
- data/lib/sqa/seasonal_analyzer.rb +209 -0
- data/lib/sqa/sector_analyzer.rb +300 -0
- data/lib/sqa/stock.rb +67 -96
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- data/lib/sqa/strategy/common.rb +0 -2
- data/lib/sqa/strategy/consensus.rb +5 -2
- data/lib/sqa/strategy/kbs_strategy.rb +470 -0
- data/lib/sqa/strategy/macd.rb +46 -0
- data/lib/sqa/strategy/mp.rb +1 -1
- data/lib/sqa/strategy/stochastic.rb +60 -0
- data/lib/sqa/strategy/volume_breakout.rb +57 -0
- data/lib/sqa/strategy.rb +5 -0
- data/lib/sqa/strategy_generator.rb +947 -0
- data/lib/sqa/stream.rb +361 -0
- data/lib/sqa/version.rb +1 -7
- data/lib/sqa.rb +41 -14
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +279 -48
- data/bin/sqa +0 -6
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/analysis.rb +0 -306
- data/lib/sqa/cli.rb +0 -173
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -43
- data/lib/sqa/indicator/bollinger_bands.rb +0 -28
- data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
- data/lib/sqa/indicator/donchian_channel.rb +0 -29
- data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
- data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
- data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
- data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
- data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
- data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
- data/lib/sqa/indicator/market_profile.rb +0 -32
- data/lib/sqa/indicator/mean_reversion.rb +0 -37
- data/lib/sqa/indicator/momentum.rb +0 -28
- data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
- data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
- data/lib/sqa/indicator/predict_next_value.rb +0 -202
- data/lib/sqa/indicator/relative_strength_index.rb +0 -47
- data/lib/sqa/indicator/simple_moving_average.rb +0 -24
- data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
- data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
- data/lib/sqa/indicator/true_range.rb +0 -39
- data/lib/sqa/trade.rb +0 -26
- data/lib/sqa/web.rb +0 -159
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/elliott_wave_theory.rb
|
|
2
|
-
|
|
3
|
-
# TIDI: This is very simplistic. It may be completely wrong even
|
|
4
|
-
# as simplistic as it is. Consider using the sma_trend to
|
|
5
|
-
# acquire the up and down patterns. Run those through a
|
|
6
|
-
# classifier. Might even have to review the concept of a
|
|
7
|
-
# trend with regard to varying the periods to turn
|
|
8
|
-
# many small patterns into fewer larger patterns. Then
|
|
9
|
-
# maybe the 12345 and abc patterns will be extractable.
|
|
10
|
-
# On the whole I think the consensus is that EWT is not
|
|
11
|
-
# that useful for predictive trading.
|
|
12
|
-
|
|
13
|
-
class SQA::Indicator; class << self
|
|
14
|
-
|
|
15
|
-
def elliott_wave_theory(
|
|
16
|
-
prices # Array of prices
|
|
17
|
-
)
|
|
18
|
-
waves = []
|
|
19
|
-
wave_start = 0
|
|
20
|
-
|
|
21
|
-
(1..prices.length-2).each do |x|
|
|
22
|
-
turning_point = prices[x] > prices[x-1] && prices[x] > prices[x+1] ||
|
|
23
|
-
prices[x] < prices[x-1] && prices[x] < prices[x+1]
|
|
24
|
-
|
|
25
|
-
if turning_point
|
|
26
|
-
waves << prices[wave_start..x]
|
|
27
|
-
wave_start = x + 1
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
analysis = []
|
|
32
|
-
|
|
33
|
-
waves.each do |wave|
|
|
34
|
-
analysis << {
|
|
35
|
-
wave: wave,
|
|
36
|
-
pattern: ewt_identify_pattern(wave)
|
|
37
|
-
}
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
analysis
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
private def ewt_identify_pattern(wave)
|
|
45
|
-
if wave.length == 5
|
|
46
|
-
:impulse
|
|
47
|
-
elsif wave.length == 3
|
|
48
|
-
:corrective_zigzag
|
|
49
|
-
elsif wave.length > 5
|
|
50
|
-
:corrective_complex
|
|
51
|
-
else
|
|
52
|
-
:unknown
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
end; end
|
|
57
|
-
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/exponential_moving_average.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def exponential_moving_average(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period # Integer number of entries to consider
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
ema_values = []
|
|
11
|
-
ema_values << prices.first
|
|
12
|
-
|
|
13
|
-
multiplier = (2.0 / (period + 1))
|
|
14
|
-
|
|
15
|
-
(1...prices.length).each do |x|
|
|
16
|
-
ema = (prices[x] - ema_values.last) * multiplier + ema_values.last
|
|
17
|
-
ema_values << ema
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
ema_values
|
|
21
|
-
end
|
|
22
|
-
alias_method :ema, :exponential_moving_average
|
|
23
|
-
|
|
24
|
-
end; end
|
|
25
|
-
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/exponential_moving_average_trend.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def exponential_moving_average_trend(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period # Integer number of entries to consider
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
ema_values = exponential_moving_average(
|
|
11
|
-
prices,
|
|
12
|
-
period
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
last_ema = ema_values.last
|
|
16
|
-
previous_ema = ema_values[-2]
|
|
17
|
-
|
|
18
|
-
trend = if last_ema > previous_ema
|
|
19
|
-
:up
|
|
20
|
-
elsif last_ema < previous_ema
|
|
21
|
-
:down
|
|
22
|
-
else
|
|
23
|
-
:neutral
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
ema: ema_values,
|
|
28
|
-
trend: trend,
|
|
29
|
-
support: ema_values.min,
|
|
30
|
-
resistance: ema_values.max
|
|
31
|
-
}
|
|
32
|
-
end
|
|
33
|
-
alias_method :ema_trend, :exponential_moving_average_trend
|
|
34
|
-
|
|
35
|
-
end; end
|
|
36
|
-
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/fibonacci_retracement.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def fibonacci_retracement(
|
|
6
|
-
swing_high, # Float peak price in a period - peak
|
|
7
|
-
swing_low # Float bottom price in a period - valley
|
|
8
|
-
)
|
|
9
|
-
retracement_levels = []
|
|
10
|
-
|
|
11
|
-
fibonacci_levels = [0.236, 0.382, 0.5, 0.618, 0.786]
|
|
12
|
-
|
|
13
|
-
fibonacci_levels.each do |level|
|
|
14
|
-
retracement_levels << swing_low + (swing_high - swing_low) * level
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
retracement_levels # Array
|
|
19
|
-
end
|
|
20
|
-
alias_method :fr, :fibonacci_retracement
|
|
21
|
-
|
|
22
|
-
end; end
|
|
23
|
-
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/head_and_shoulders_pattern.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def head_and_shoulders_pattern?(
|
|
7
|
-
prices # Array of prices
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
return false if prices.length < 5
|
|
11
|
-
|
|
12
|
-
data = prices.last(5)
|
|
13
|
-
|
|
14
|
-
left_shoulder = data[0]
|
|
15
|
-
head = data[1]
|
|
16
|
-
right_shoulder = data[2]
|
|
17
|
-
neckline = data[3]
|
|
18
|
-
right_peak = data[4]
|
|
19
|
-
|
|
20
|
-
head > left_shoulder &&
|
|
21
|
-
head > right_shoulder &&
|
|
22
|
-
right_peak < neckline
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
end; end
|
|
26
|
-
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/market_profile.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def market_profile(
|
|
6
|
-
volumes, # Array of volumes
|
|
7
|
-
prices, # Array of prices
|
|
8
|
-
support_threshold, # Float stock's support price estimate
|
|
9
|
-
resistance_threshold # Float stock's resistance price estimate
|
|
10
|
-
)
|
|
11
|
-
total_volume = volumes.sum
|
|
12
|
-
average_volume = volumes.mean
|
|
13
|
-
max_volume = volumes.max
|
|
14
|
-
|
|
15
|
-
support_levels = prices.select { |price| price <= support_threshold }
|
|
16
|
-
resistance_levels = prices.select { |price| price >= resistance_threshold }
|
|
17
|
-
|
|
18
|
-
if support_levels.empty? &&
|
|
19
|
-
resistance_levels.empty?
|
|
20
|
-
:neutral
|
|
21
|
-
elsif support_levels.empty?
|
|
22
|
-
:resistance
|
|
23
|
-
elsif resistance_levels.empty?
|
|
24
|
-
:support
|
|
25
|
-
else
|
|
26
|
-
:mixed
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
alias_method :mp, :market_profile
|
|
30
|
-
|
|
31
|
-
end; end
|
|
32
|
-
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/mean_reversion.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
# @param prices [Array]
|
|
6
|
-
# @param lookback_period [Integer]
|
|
7
|
-
# @param deviation_threshold [Float]
|
|
8
|
-
#
|
|
9
|
-
# @return [Boolean] True if the stock exhibits mean reversion behavior,
|
|
10
|
-
# false otherwise.
|
|
11
|
-
#
|
|
12
|
-
def mean_reversion?(
|
|
13
|
-
prices, # Array of prices
|
|
14
|
-
lookback_period, # Integer number of events to consider
|
|
15
|
-
deviation_threshold # Float delta change at which a price is considered to be over extended
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
return false if prices.length < lookback_period
|
|
19
|
-
|
|
20
|
-
mean = mr_mean(prices, lookback_period)
|
|
21
|
-
deviation = prices[-1] - mean
|
|
22
|
-
|
|
23
|
-
if deviation.abs > deviation_threshold
|
|
24
|
-
return true
|
|
25
|
-
else
|
|
26
|
-
return false
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def mr_mean(prices, lookback_period)
|
|
32
|
-
prices.last(lookback_period).sum / lookback_period.to_f
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
end; end
|
|
36
|
-
|
|
37
|
-
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/momentum.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
# @param prices [Array]
|
|
6
|
-
# @param period [Integer]
|
|
7
|
-
#
|
|
8
|
-
# @return [Float]
|
|
9
|
-
#
|
|
10
|
-
def momentum(
|
|
11
|
-
prices, # Array of prices
|
|
12
|
-
period # Integer number of entries to consider
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
momentums = []
|
|
16
|
-
|
|
17
|
-
prices.each_cons(period) do |window|
|
|
18
|
-
current_price = window.last.to_f
|
|
19
|
-
past_price = window.first.to_f
|
|
20
|
-
momentums << 10.0 * ( (current_price - past_price) / past_price)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
momentums # Array
|
|
24
|
-
end
|
|
25
|
-
alias_method :m, :momentum
|
|
26
|
-
|
|
27
|
-
end; end
|
|
28
|
-
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/moving_average_convergence_divergence.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def moving_average_convergence_divergence(
|
|
6
|
-
prices,
|
|
7
|
-
short_period,
|
|
8
|
-
long_period,
|
|
9
|
-
signal_period
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
short_ma = simple_moving_average(prices, short_period)
|
|
13
|
-
long_ma = simple_moving_average(prices, long_period)
|
|
14
|
-
signal_line = simple_moving_average(short_ma, signal_period)
|
|
15
|
-
macd_line = []
|
|
16
|
-
|
|
17
|
-
prices.size.times do |x|
|
|
18
|
-
macd_line << short_ma[x] - long_ma[x]
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
{
|
|
22
|
-
macd: macd_line, # Array
|
|
23
|
-
signal: signal_line # Array
|
|
24
|
-
}
|
|
25
|
-
end
|
|
26
|
-
alias_method :macd, :moving_average_convergence_divergence
|
|
27
|
-
|
|
28
|
-
end; end
|
|
29
|
-
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
class SQA::Indicator; class << self
|
|
3
|
-
|
|
4
|
-
def peaks_and_valleys(
|
|
5
|
-
prices, # Array of prices
|
|
6
|
-
delta # Integer distance delta (# of higher or lower prices to either side)
|
|
7
|
-
)
|
|
8
|
-
peaks = []
|
|
9
|
-
valleys = []
|
|
10
|
-
period = 2 * delta + 1
|
|
11
|
-
|
|
12
|
-
prices.each_cons(period) do |window|
|
|
13
|
-
price = window[delta]
|
|
14
|
-
|
|
15
|
-
next if window.count(price) == period
|
|
16
|
-
|
|
17
|
-
peaks << price if window.max == price
|
|
18
|
-
valleys << price if window.min == price
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
{
|
|
22
|
-
period: period,
|
|
23
|
-
peaks: peaks,
|
|
24
|
-
valleys: valleys
|
|
25
|
-
}
|
|
26
|
-
end
|
|
27
|
-
alias_method :pav, :peaks_and_valleys
|
|
28
|
-
|
|
29
|
-
end; end
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/predict_next_values.rb
|
|
2
|
-
|
|
3
|
-
module SQA
|
|
4
|
-
end
|
|
5
|
-
|
|
6
|
-
class SQA::Indicator; class << self
|
|
7
|
-
|
|
8
|
-
# Produce a Table show actual values and forecasted values
|
|
9
|
-
#
|
|
10
|
-
# actual .... Array of Float
|
|
11
|
-
# forecast .. Array of Float or Array of Array of Float
|
|
12
|
-
# entry is either a single value or
|
|
13
|
-
# an Array [high, guess, low]
|
|
14
|
-
#
|
|
15
|
-
def prediction_test(actual, forecast)
|
|
16
|
-
|
|
17
|
-
unless actual.size == forecast.size
|
|
18
|
-
debug_me("== ERROR =="){[
|
|
19
|
-
"actual.size",
|
|
20
|
-
"forecast.size"
|
|
21
|
-
]}
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Method Under Test (MUT)
|
|
25
|
-
mut = caller[0][/`([^']*)'/, 1]
|
|
26
|
-
window = actual.size
|
|
27
|
-
hgl = forecast.first.is_a?(Array)
|
|
28
|
-
|
|
29
|
-
if hgl
|
|
30
|
-
headers = %w[ Actual Forecast Diff %off InRange? High Low ]
|
|
31
|
-
else
|
|
32
|
-
headers = %w[ Actual Forecast Diff %off ]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
diff = []
|
|
36
|
-
percent = []
|
|
37
|
-
values = []
|
|
38
|
-
|
|
39
|
-
actual.map!{|v| v.round(3)}
|
|
40
|
-
|
|
41
|
-
if hgl
|
|
42
|
-
high = forecast.map{|v| v[0].round(3)}
|
|
43
|
-
guess = forecast.map{|v| v[1].round(3)}
|
|
44
|
-
low = forecast.map{|v| v[2].round(3)}
|
|
45
|
-
else
|
|
46
|
-
guess = forecast.map{|v| v.round(3)}
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
window.times do |x|
|
|
50
|
-
diff << (actual[x] - guess[x]).round(3)
|
|
51
|
-
percent << ((diff.last / guess[x])*100.0).round(3)
|
|
52
|
-
|
|
53
|
-
entry = [
|
|
54
|
-
actual[x], guess[x],
|
|
55
|
-
diff[x], percent[x],
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
if hgl
|
|
59
|
-
entry << ( (high[x] >= actual[x] && actual[x] >= low[x]) ? "Yes" : "" )
|
|
60
|
-
entry << high[x]
|
|
61
|
-
entry << low[x]
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
values << entry
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
the_table = TTY::Table.new(headers, values)
|
|
68
|
-
|
|
69
|
-
puts "\n#{mut} Result Validation"
|
|
70
|
-
|
|
71
|
-
puts the_table.render(
|
|
72
|
-
:unicode,
|
|
73
|
-
{
|
|
74
|
-
padding: [0, 0, 0, 0],
|
|
75
|
-
alignments: [:right]*values.first.size,
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
puts
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def predict_next_values(stock, window, testing=false)
|
|
83
|
-
prices = stock.df.adj_close_price.to_a
|
|
84
|
-
known = prices.pop(window) if testing
|
|
85
|
-
result = []
|
|
86
|
-
|
|
87
|
-
prices.each_cons(2) do |a, b|
|
|
88
|
-
result << b + (b - a)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
if window > 0
|
|
92
|
-
(1..window).each do |_|
|
|
93
|
-
last_two_values = result.last(2)
|
|
94
|
-
delta = last_two_values.last - last_two_values.first
|
|
95
|
-
next_value = last_two_values.last + delta
|
|
96
|
-
result << next_value
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
prediction_test(known, result.last(window)) if testing
|
|
101
|
-
|
|
102
|
-
result.last(window)
|
|
103
|
-
end
|
|
104
|
-
alias_method :pnv, :predict_next_values
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def pnv2(stock, window, testing=false)
|
|
108
|
-
prices = stock.df.adj_close_price.to_a
|
|
109
|
-
known = prices.pop(window) if testing
|
|
110
|
-
|
|
111
|
-
result = []
|
|
112
|
-
last_inx = prices.size - 1 # indexes are zero based
|
|
113
|
-
|
|
114
|
-
window.times do |x|
|
|
115
|
-
x += 1 # forecasting 1 day into the future needs 2 days of near past data
|
|
116
|
-
|
|
117
|
-
# window is the near past values
|
|
118
|
-
window = prices[last_inx-x..]
|
|
119
|
-
|
|
120
|
-
high = window.max
|
|
121
|
-
low = window.min
|
|
122
|
-
midpoint = (high + low) / 2.0
|
|
123
|
-
|
|
124
|
-
result << [high, midpoint, low]
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
prediction_test(known, result) if testing
|
|
128
|
-
|
|
129
|
-
result
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def pnv3(stock, window, testing=false)
|
|
134
|
-
prices = stock.df.adj_close_price.to_a
|
|
135
|
-
known = prices.pop(window) if testing
|
|
136
|
-
|
|
137
|
-
result = []
|
|
138
|
-
known = prices.last(window)
|
|
139
|
-
|
|
140
|
-
last_inx = prices.size - 1
|
|
141
|
-
|
|
142
|
-
(0..window-1).to_a.reverse.each do |x|
|
|
143
|
-
curr_inx = last_inx - x
|
|
144
|
-
prev_inx = curr_inx - 1
|
|
145
|
-
current_price = prices[curr_inx]
|
|
146
|
-
percentage_change = (current_price - prices[prev_inx]) / prices[prev_inx]
|
|
147
|
-
|
|
148
|
-
result << current_price + (current_price * percentage_change)
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
prediction_test(known, result) if testing
|
|
152
|
-
|
|
153
|
-
result
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def pnv4(stock, window, testing=false)
|
|
158
|
-
prices = stock.df.adj_close_price.to_a
|
|
159
|
-
known = prices.pop(window) if testing
|
|
160
|
-
|
|
161
|
-
result = []
|
|
162
|
-
known = prices.last(window).dup
|
|
163
|
-
current_price = known.last
|
|
164
|
-
|
|
165
|
-
# Loop through the prediction window size
|
|
166
|
-
(1..window).each do |x|
|
|
167
|
-
|
|
168
|
-
# Calculate the percentage change between the current price and its previous price
|
|
169
|
-
percentage_change = (current_price - prices[-x]) / prices[-x]
|
|
170
|
-
|
|
171
|
-
result << current_price + (current_price * percentage_change)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
prediction_test(known, result) if testing
|
|
175
|
-
|
|
176
|
-
result
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def pnv5(stock, window, testing=false)
|
|
181
|
-
prices = stock.df.adj_close_price.to_a
|
|
182
|
-
known = prices.pop(window) if testing
|
|
183
|
-
|
|
184
|
-
result = []
|
|
185
|
-
current_price = prices.last
|
|
186
|
-
|
|
187
|
-
rate = 0.9 # convert angle into percentage
|
|
188
|
-
sma_trend = stock.indicators.sma_trend
|
|
189
|
-
percentage_change = 1 + (sma_trend[:angle] / 100.0) * rate
|
|
190
|
-
|
|
191
|
-
# Assumes the SMA trend will continue
|
|
192
|
-
window.times do |_|
|
|
193
|
-
result << current_price * percentage_change
|
|
194
|
-
current_price = result.last
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
prediction_test(known, result) if testing
|
|
198
|
-
|
|
199
|
-
result
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
end; end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/relative_strength_index.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def relative_strength_index(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period, # Integer how many to consider at a time
|
|
8
|
-
over_sold = 30.0, # Float break over point in trend
|
|
9
|
-
over_bought = 70.0 # Float break over point in trend
|
|
10
|
-
)
|
|
11
|
-
gains = []
|
|
12
|
-
losses = []
|
|
13
|
-
|
|
14
|
-
prices.each_cons(2) do |pair|
|
|
15
|
-
change = pair[1] - pair[0]
|
|
16
|
-
|
|
17
|
-
if change > 0
|
|
18
|
-
gains << change
|
|
19
|
-
losses << 0
|
|
20
|
-
else
|
|
21
|
-
gains << 0
|
|
22
|
-
losses << change.abs
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
avg_gain = gains.last(period).sum / period.to_f
|
|
27
|
-
avg_loss = losses.last(period).sum / period.to_f
|
|
28
|
-
rs = avg_gain / avg_loss
|
|
29
|
-
rsi = 100 - (100 / (1 + rs))
|
|
30
|
-
|
|
31
|
-
trend = if rsi >= over_bought
|
|
32
|
-
:over_bought
|
|
33
|
-
elsif rsi <= over_sold
|
|
34
|
-
:over_sold
|
|
35
|
-
else
|
|
36
|
-
:normal
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
rsi: rsi, # Float
|
|
41
|
-
trend: trend # Symbol :normal, :over_bought, :over+sold
|
|
42
|
-
}
|
|
43
|
-
end
|
|
44
|
-
alias_method :rsi, :relative_strength_index
|
|
45
|
-
|
|
46
|
-
end; end
|
|
47
|
-
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/simple_moving_average.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def simple_moving_average(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period # Integer how many to consider at a time
|
|
8
|
-
)
|
|
9
|
-
moving_averages = []
|
|
10
|
-
|
|
11
|
-
(0..period-2).to_a.each do |x|
|
|
12
|
-
moving_averages << prices[0..x].mean
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
prices.each_cons(period) do |window|
|
|
16
|
-
moving_averages << window.mean
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
moving_averages # Array
|
|
20
|
-
end
|
|
21
|
-
alias_method :sma, :simple_moving_average
|
|
22
|
-
|
|
23
|
-
end; end
|
|
24
|
-
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# lib/sqa/indicator/simple_moving_average_trend.rb
|
|
2
|
-
|
|
3
|
-
class SQA::Indicator; class << self
|
|
4
|
-
|
|
5
|
-
def simple_moving_average_trend(
|
|
6
|
-
prices, # Array of prices
|
|
7
|
-
period, # Integer number of entries to consider
|
|
8
|
-
delta = 1.0 # Float defines the angle range(+/-) for :neutral trend
|
|
9
|
-
)
|
|
10
|
-
sma = simple_moving_average(prices, period)
|
|
11
|
-
last_sma = sma.last
|
|
12
|
-
prev_sma = sma.last(period).first
|
|
13
|
-
angle = Math.atan((last_sma - prev_sma) / period) * (180 / Math::PI)
|
|
14
|
-
|
|
15
|
-
trend = if angle > delta
|
|
16
|
-
:up
|
|
17
|
-
elsif angle < -delta
|
|
18
|
-
:down
|
|
19
|
-
else
|
|
20
|
-
:neutral
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
sma: sma, # Array
|
|
25
|
-
trend: trend, # Symbol :up, :down, :neutral
|
|
26
|
-
angle: angle # Float how step the trend
|
|
27
|
-
}
|
|
28
|
-
end
|
|
29
|
-
alias_method :sma_trend, :simple_moving_average_trend
|
|
30
|
-
|
|
31
|
-
end; end
|
|
32
|
-
|