sqa 0.0.24 → 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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.goose/memory/development.txt +3 -0
  3. data/.semver +6 -0
  4. data/ARCHITECTURE.md +648 -0
  5. data/CHANGELOG.md +82 -0
  6. data/CLAUDE.md +653 -0
  7. data/COMMITS.md +196 -0
  8. data/DATAFRAME_ARCHITECTURE_REVIEW.md +421 -0
  9. data/NEXT-STEPS.md +154 -0
  10. data/README.md +812 -262
  11. data/TASKS.md +358 -0
  12. data/TEST_RESULTS.md +140 -0
  13. data/TODO.md +42 -0
  14. data/_notes.txt +25 -0
  15. data/bin/sqa-console +11 -0
  16. data/data/talk_talk.json +103284 -0
  17. data/develop_summary.md +313 -0
  18. data/docs/advanced/backtesting.md +206 -0
  19. data/docs/advanced/ensemble.md +68 -0
  20. data/docs/advanced/fpop.md +153 -0
  21. data/docs/advanced/index.md +112 -0
  22. data/docs/advanced/multi-timeframe.md +67 -0
  23. data/docs/advanced/pattern-matcher.md +75 -0
  24. data/docs/advanced/portfolio-optimizer.md +79 -0
  25. data/docs/advanced/portfolio.md +166 -0
  26. data/docs/advanced/risk-management.md +210 -0
  27. data/docs/advanced/strategy-generator.md +158 -0
  28. data/docs/advanced/streaming.md +209 -0
  29. data/docs/ai_and_ml.md +80 -0
  30. data/docs/api/dataframe.md +1115 -0
  31. data/docs/api/index.md +126 -0
  32. data/docs/assets/css/custom.css +88 -0
  33. data/docs/assets/js/mathjax.js +18 -0
  34. data/docs/concepts/index.md +68 -0
  35. data/docs/contributing/index.md +60 -0
  36. data/docs/data-sources/index.md +66 -0
  37. data/docs/data_frame.md +317 -97
  38. data/docs/factors_that_impact_price.md +26 -0
  39. data/docs/finviz.md +11 -0
  40. data/docs/fx_pro_bit.md +25 -0
  41. data/docs/genetic_programming.md +104 -0
  42. data/docs/getting-started/index.md +123 -0
  43. data/docs/getting-started/installation.md +229 -0
  44. data/docs/getting-started/quick-start.md +244 -0
  45. data/docs/i_gotta_an_idea.md +22 -0
  46. data/docs/index.md +163 -0
  47. data/docs/indicators/index.md +97 -0
  48. data/docs/indicators.md +110 -24
  49. data/docs/options.md +8 -0
  50. data/docs/strategies/bollinger-bands.md +146 -0
  51. data/docs/strategies/consensus.md +64 -0
  52. data/docs/strategies/custom.md +310 -0
  53. data/docs/strategies/ema.md +53 -0
  54. data/docs/strategies/index.md +92 -0
  55. data/docs/strategies/kbs.md +164 -0
  56. data/docs/strategies/macd.md +96 -0
  57. data/docs/strategies/market-profile.md +54 -0
  58. data/docs/strategies/mean-reversion.md +58 -0
  59. data/docs/strategies/rsi.md +95 -0
  60. data/docs/strategies/sma.md +55 -0
  61. data/docs/strategies/stochastic.md +63 -0
  62. data/docs/strategies/volume-breakout.md +54 -0
  63. data/docs/tags.md +7 -0
  64. data/docs/true_strength_index.md +46 -0
  65. data/docs/weighted_moving_average.md +48 -0
  66. data/examples/README.md +354 -0
  67. data/examples/advanced_features_example.rb +350 -0
  68. data/examples/fpop_analysis_example.rb +191 -0
  69. data/examples/genetic_programming_example.rb +148 -0
  70. data/examples/kbs_strategy_example.rb +208 -0
  71. data/examples/pattern_context_example.rb +300 -0
  72. data/examples/rails_app/Gemfile +34 -0
  73. data/examples/rails_app/README.md +416 -0
  74. data/examples/rails_app/app/assets/javascripts/application.js +107 -0
  75. data/examples/rails_app/app/assets/stylesheets/application.css +659 -0
  76. data/examples/rails_app/app/controllers/analysis_controller.rb +11 -0
  77. data/examples/rails_app/app/controllers/api/v1/stocks_controller.rb +227 -0
  78. data/examples/rails_app/app/controllers/application_controller.rb +22 -0
  79. data/examples/rails_app/app/controllers/backtest_controller.rb +11 -0
  80. data/examples/rails_app/app/controllers/dashboard_controller.rb +21 -0
  81. data/examples/rails_app/app/controllers/portfolio_controller.rb +7 -0
  82. data/examples/rails_app/app/views/analysis/show.html.erb +209 -0
  83. data/examples/rails_app/app/views/backtest/show.html.erb +171 -0
  84. data/examples/rails_app/app/views/dashboard/index.html.erb +118 -0
  85. data/examples/rails_app/app/views/dashboard/show.html.erb +408 -0
  86. data/examples/rails_app/app/views/errors/show.html.erb +17 -0
  87. data/examples/rails_app/app/views/layouts/application.html.erb +60 -0
  88. data/examples/rails_app/app/views/portfolio/index.html.erb +33 -0
  89. data/examples/rails_app/bin/rails +6 -0
  90. data/examples/rails_app/config/application.rb +45 -0
  91. data/examples/rails_app/config/boot.rb +5 -0
  92. data/examples/rails_app/config/database.yml +18 -0
  93. data/examples/rails_app/config/environment.rb +11 -0
  94. data/examples/rails_app/config/routes.rb +26 -0
  95. data/examples/rails_app/config.ru +8 -0
  96. data/examples/realtime_stream_example.rb +274 -0
  97. data/examples/sinatra_app/Gemfile +22 -0
  98. data/examples/sinatra_app/QUICKSTART.md +159 -0
  99. data/examples/sinatra_app/README.md +461 -0
  100. data/examples/sinatra_app/app.rb +344 -0
  101. data/examples/sinatra_app/config.ru +5 -0
  102. data/examples/sinatra_app/public/css/style.css +659 -0
  103. data/examples/sinatra_app/public/js/app.js +107 -0
  104. data/examples/sinatra_app/views/analyze.erb +306 -0
  105. data/examples/sinatra_app/views/backtest.erb +325 -0
  106. data/examples/sinatra_app/views/dashboard.erb +419 -0
  107. data/examples/sinatra_app/views/error.erb +58 -0
  108. data/examples/sinatra_app/views/index.erb +118 -0
  109. data/examples/sinatra_app/views/layout.erb +61 -0
  110. data/examples/sinatra_app/views/portfolio.erb +43 -0
  111. data/examples/strategy_generator_example.rb +346 -0
  112. data/hsa_portfolio.csv +11 -0
  113. data/justfile +0 -0
  114. data/lib/api/alpha_vantage_api.rb +462 -0
  115. data/lib/sqa/backtest.rb +329 -0
  116. data/lib/sqa/data_frame/alpha_vantage.rb +43 -65
  117. data/lib/sqa/data_frame/data.rb +92 -0
  118. data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
  119. data/lib/sqa/data_frame.rb +148 -243
  120. data/lib/sqa/ensemble.rb +359 -0
  121. data/lib/sqa/fpop.rb +199 -0
  122. data/lib/sqa/gp.rb +259 -0
  123. data/lib/sqa/indicator.rb +5 -8
  124. data/lib/sqa/init.rb +15 -8
  125. data/lib/sqa/market_regime.rb +240 -0
  126. data/lib/sqa/multi_timeframe.rb +379 -0
  127. data/lib/sqa/pattern_matcher.rb +497 -0
  128. data/lib/sqa/portfolio.rb +260 -6
  129. data/lib/sqa/portfolio_optimizer.rb +377 -0
  130. data/lib/sqa/risk_manager.rb +442 -0
  131. data/lib/sqa/seasonal_analyzer.rb +209 -0
  132. data/lib/sqa/sector_analyzer.rb +300 -0
  133. data/lib/sqa/stock.rb +67 -125
  134. data/lib/sqa/strategy/bollinger_bands.rb +42 -0
  135. data/lib/sqa/strategy/consensus.rb +5 -2
  136. data/lib/sqa/strategy/kbs_strategy.rb +470 -0
  137. data/lib/sqa/strategy/macd.rb +46 -0
  138. data/lib/sqa/strategy/mp.rb +1 -1
  139. data/lib/sqa/strategy/stochastic.rb +60 -0
  140. data/lib/sqa/strategy/volume_breakout.rb +57 -0
  141. data/lib/sqa/strategy.rb +5 -0
  142. data/lib/sqa/strategy_generator.rb +947 -0
  143. data/lib/sqa/stream.rb +361 -0
  144. data/lib/sqa/version.rb +1 -7
  145. data/lib/sqa.rb +23 -16
  146. data/main.just +81 -0
  147. data/mkdocs.yml +288 -0
  148. data/trace.log +0 -0
  149. metadata +261 -51
  150. data/bin/sqa +0 -6
  151. data/lib/patches/dry-cli.rb +0 -228
  152. data/lib/sqa/activity.rb +0 -10
  153. data/lib/sqa/cli.rb +0 -62
  154. data/lib/sqa/commands/analysis.rb +0 -309
  155. data/lib/sqa/commands/base.rb +0 -139
  156. data/lib/sqa/commands/web.rb +0 -199
  157. data/lib/sqa/commands.rb +0 -22
  158. data/lib/sqa/constants.rb +0 -23
  159. data/lib/sqa/indicator/average_true_range.rb +0 -33
  160. data/lib/sqa/indicator/bollinger_bands.rb +0 -28
  161. data/lib/sqa/indicator/candlestick_pattern_recognizer.rb +0 -60
  162. data/lib/sqa/indicator/donchian_channel.rb +0 -29
  163. data/lib/sqa/indicator/double_top_bottom_pattern.rb +0 -34
  164. data/lib/sqa/indicator/elliott_wave_theory.rb +0 -57
  165. data/lib/sqa/indicator/exponential_moving_average.rb +0 -25
  166. data/lib/sqa/indicator/exponential_moving_average_trend.rb +0 -36
  167. data/lib/sqa/indicator/fibonacci_retracement.rb +0 -23
  168. data/lib/sqa/indicator/head_and_shoulders_pattern.rb +0 -26
  169. data/lib/sqa/indicator/market_profile.rb +0 -32
  170. data/lib/sqa/indicator/mean_reversion.rb +0 -37
  171. data/lib/sqa/indicator/momentum.rb +0 -28
  172. data/lib/sqa/indicator/moving_average_convergence_divergence.rb +0 -29
  173. data/lib/sqa/indicator/peaks_and_valleys.rb +0 -29
  174. data/lib/sqa/indicator/predict_next_value.rb +0 -202
  175. data/lib/sqa/indicator/relative_strength_index.rb +0 -47
  176. data/lib/sqa/indicator/simple_moving_average.rb +0 -24
  177. data/lib/sqa/indicator/simple_moving_average_trend.rb +0 -32
  178. data/lib/sqa/indicator/stochastic_oscillator.rb +0 -68
  179. data/lib/sqa/indicator/true_range.rb +0 -39
  180. data/lib/sqa/trade.rb +0 -26
@@ -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
-
@@ -1,68 +0,0 @@
1
- # lib/sqa/indicator/stochastic_oscillator.rb
2
-
3
- class SQA::Indicator; class << self
4
-
5
- # @param high_prices [Array]
6
- # @param low_prices [Array]
7
- # @param close_prices [Array]
8
- # @param period [Integer]
9
- # @param smoothing_period [Integer]
10
- #
11
- # @return [Array] An array of %K and %D values.
12
- #
13
- def stochastic_oscillator(
14
- high_prices, # Array of high prices
15
- low_prices, # Array of low prices
16
- closing_prices, # Array of closing prices
17
- period, # Integer The period for calculating the Stochastic Oscillator
18
- smoothing_period # Integer The smoothing period for %K line
19
- )
20
- k_values = []
21
- d_values = []
22
-
23
- closing_prices.each_cons(period) do |window|
24
- highest_high = high_prices.max(period)
25
- lowest_low = low_prices.min(period)
26
- current_close = window.last
27
- k_values << (current_close - lowest_low) / (highest_high - lowest_low) * 100 # Calculate the k_value
28
- end
29
-
30
- k_values.each_cons(smoothing_period) do |k_values_subset|
31
- d_values << k_values_subset.sum / smoothing_period.to_f # Calculate the d_value
32
- end
33
-
34
- [k_values, d_values]
35
- end
36
- alias_method :so, :stochastic_oscillator
37
-
38
-
39
- def stochastic_oscillator2(
40
- prices, # Array of prices
41
- period # Integer number of events to consider
42
- )
43
- k_values = []
44
- d_values = []
45
-
46
- prices.each_cons(period) do |window|
47
- low = window.min # Lowest price in the period
48
- high = window.max # Highest price in the period
49
- current_price = window.last # Current closing price
50
-
51
- k_values << (current_price - low) * 100 / (high - low)
52
- end
53
-
54
- k_values.each_cons(period) do |window|
55
- d_values << window.mean
56
- end
57
-
58
- {
59
- k: k_values,
60
- d: d_values
61
- }
62
- end
63
- alias_method :so2, :stochastic_oscillator2
64
-
65
-
66
-
67
- end; end
68
-
@@ -1,39 +0,0 @@
1
- # lib/sqa/indicator/true_range.rb
2
-
3
- # See Also: average_true_range
4
-
5
- class SQA::Indicator; class << self
6
-
7
- # @param high_prices [Array]
8
- # @param low_prices [Array]
9
- # @param previous_closes [Array]
10
- #
11
- # @return [Array]
12
- #
13
- def true_range(
14
- high_prices, # Array of high prices
15
- low_prices, # Array of low prices
16
- closing_prices # Array of closing prices
17
- )
18
- true_ranges = []
19
-
20
- high_prices.each_with_index do |high, index|
21
- if index > 0
22
- low = low_prices[index]
23
- previous_close = closing_prices[index - 1]
24
-
25
- true_range = [
26
- high - low,
27
- (high - previous_close).abs,
28
- (low - previous_close).abs
29
- ].max
30
-
31
- true_ranges << true_range
32
- end
33
- end
34
-
35
- true_ranges # Array of True Range values
36
- end
37
- alias_method :tr, :true_range
38
-
39
- end; end
data/lib/sqa/trade.rb DELETED
@@ -1,26 +0,0 @@
1
- # lib/sqa/trade.rb
2
-
3
-
4
- class SQA::Trade
5
- attr_accessor :df
6
-
7
- def initialize(
8
- filename = SQA::Config.trades_filename
9
- )
10
- @df = SQA::DataFrame.load(filename)
11
- end
12
-
13
- def place(signal, ticker, shares, price=nil)
14
- # TODO: insert row into @df
15
-
16
- uuid = rand(100000) # FIXME: place holder
17
- end
18
-
19
- def confirm(uuid, shares, price)
20
- # TODO: update the row in the data frame
21
- end
22
-
23
- def save
24
- # TODO: save the data frame
25
- end
26
- end