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
@@ -0,0 +1,470 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kbs'
4
+ require 'kbs/dsl'
5
+
6
+ =begin
7
+
8
+ Knowledge-Based Strategy using RETE Forward Chaining
9
+
10
+ This strategy uses a rule-based system with the RETE algorithm for
11
+ forward-chaining inference. It allows defining complex trading rules
12
+ that react to market conditions.
13
+
14
+ The strategy asserts facts about market conditions (RSI, trends, volume, etc.)
15
+ and fires rules when patterns are matched.
16
+
17
+ DSL Keywords:
18
+ - on : Assert a condition (fact must exist)
19
+ - without : Negated condition (fact must NOT exist)
20
+ - perform : Define action to execute when rule fires
21
+ - execute : Alias for perform
22
+ - action : Alias for perform
23
+
24
+ Example:
25
+ strategy = SQA::Strategy::KBS.new
26
+
27
+ # Capture kb for use in perform blocks
28
+ kb = strategy.kb
29
+
30
+ # Define custom rules using the DSL
31
+ strategy.add_rule :buy_oversold_uptrend do
32
+ on :rsi, { level: :oversold }
33
+ on :trend, { direction: :up }
34
+ without :position
35
+
36
+ perform do
37
+ kb.assert(:signal, { action: :buy, confidence: :high })
38
+ end
39
+ end
40
+
41
+ # Execute strategy
42
+ signal = strategy.trade(vector)
43
+
44
+ Note: Use 'kb.assert' (not just 'assert') in perform blocks to access the knowledge base.
45
+
46
+ =end
47
+
48
+ module SQA
49
+ class Strategy
50
+ class KBS
51
+ attr_reader :kb, :default_rules_loaded
52
+
53
+ def initialize(load_defaults: true)
54
+ @kb = ::KBS::DSL::KnowledgeBase.new
55
+ @default_rules_loaded = false
56
+ @last_signal = :hold
57
+
58
+ load_default_rules if load_defaults
59
+ end
60
+
61
+ # Main strategy interface - compatible with SQA::Strategy framework
62
+ def self.trade(vector)
63
+ strategy = new
64
+ strategy.execute(vector)
65
+ end
66
+
67
+ # Execute strategy with given market data
68
+ def execute(vector)
69
+ # Reset working memory
70
+ @kb.reset
71
+
72
+ # Assert facts from vector
73
+ assert_market_facts(vector)
74
+
75
+ # Run the inference engine
76
+ @kb.run
77
+
78
+ # Query for trading signal
79
+ determine_signal
80
+ end
81
+
82
+ # Add a custom trading rule
83
+ #
84
+ # Example:
85
+ # add_rule :buy_dip do
86
+ # on :rsi, { value: ->(v) { v < 30 } }
87
+ # on :macd, { signal: :bullish }
88
+ # perform { kb.assert(:signal, { action: :buy, confidence: :high }) }
89
+ # end
90
+ #
91
+ # Note: Use `kb.assert` in perform blocks, not just `assert`
92
+ def add_rule(name, &block)
93
+ # Capture kb reference for use in perform blocks
94
+ kb = @kb
95
+
96
+ # Define the rule with kb available in closure
97
+ @kb.instance_eval do
98
+ rule(name, &block)
99
+ end
100
+
101
+ self
102
+ end
103
+
104
+ # Assert a fact into working memory
105
+ def assert_fact(type, attributes = {})
106
+ @kb.assert(type, attributes)
107
+ end
108
+
109
+ # Query facts from working memory
110
+ def query_facts(type, pattern = {})
111
+ @kb.query(type, pattern)
112
+ end
113
+
114
+ # Print current working memory (for debugging)
115
+ def print_facts
116
+ @kb.print_facts
117
+ end
118
+
119
+ # Print all rules (for debugging)
120
+ def print_rules
121
+ @kb.print_rules
122
+ end
123
+
124
+ private
125
+
126
+ # Assert market condition facts from the data vector
127
+ def assert_market_facts(vector)
128
+ # RSI facts
129
+ if vector.respond_to?(:rsi) && vector.rsi
130
+ rsi_value = Array(vector.rsi).last
131
+
132
+ assert_fact(:rsi, {
133
+ value: rsi_value,
134
+ level: rsi_level(rsi_value)
135
+ })
136
+ end
137
+
138
+ # MACD facts
139
+ if vector.respond_to?(:macd) && vector.macd
140
+ macd_line, signal_line, histogram = vector.macd
141
+
142
+ if macd_line && signal_line
143
+ current_macd = Array(macd_line).last
144
+ current_signal = Array(signal_line).last
145
+
146
+ assert_fact(:macd, {
147
+ line: current_macd,
148
+ signal: current_signal,
149
+ histogram: Array(histogram).last,
150
+ crossover: macd_crossover(macd_line, signal_line)
151
+ })
152
+ end
153
+ end
154
+
155
+ # Price trend facts
156
+ if vector.respond_to?(:prices) && vector.prices
157
+ prices = vector.prices
158
+
159
+ if prices.size >= 20
160
+ recent_trend = price_trend(prices.last(20))
161
+ medium_trend = price_trend(prices.last(50)) if prices.size >= 50
162
+
163
+ assert_fact(:trend, {
164
+ short_term: recent_trend,
165
+ medium_term: medium_trend || recent_trend,
166
+ strength: trend_strength(prices)
167
+ })
168
+ end
169
+ end
170
+
171
+ # SMA facts
172
+ if vector.respond_to?(:sma_short) && vector.respond_to?(:sma_long)
173
+ if vector.sma_short && vector.sma_long
174
+ short_sma = Array(vector.sma_short).last
175
+ long_sma = Array(vector.sma_long).last
176
+
177
+ assert_fact(:sma_crossover, {
178
+ short: short_sma,
179
+ long: long_sma,
180
+ signal: sma_crossover_signal(short_sma, long_sma)
181
+ })
182
+ end
183
+ end
184
+
185
+ # Volume facts
186
+ if vector.respond_to?(:volume) && vector.volume
187
+ volumes = Array(vector.volume)
188
+ current_volume = volumes.last
189
+ avg_volume = volumes.last(20).sum / 20.0 if volumes.size >= 20
190
+
191
+ if avg_volume
192
+ assert_fact(:volume, {
193
+ current: current_volume,
194
+ average: avg_volume,
195
+ level: volume_level(current_volume, avg_volume)
196
+ })
197
+ end
198
+ end
199
+
200
+ # Stochastic facts
201
+ if vector.respond_to?(:stoch_k) && vector.respond_to?(:stoch_d)
202
+ if vector.stoch_k && vector.stoch_d
203
+ k_value = Array(vector.stoch_k).last
204
+ d_value = Array(vector.stoch_d).last
205
+
206
+ assert_fact(:stochastic, {
207
+ k: k_value,
208
+ d: d_value,
209
+ zone: stoch_zone(k_value),
210
+ crossover: stoch_crossover(vector.stoch_k, vector.stoch_d)
211
+ })
212
+ end
213
+ end
214
+
215
+ # Bollinger Bands facts
216
+ if vector.respond_to?(:bb_upper) && vector.respond_to?(:bb_lower) && vector.respond_to?(:prices)
217
+ if vector.bb_upper && vector.bb_lower && vector.prices
218
+ current_price = Array(vector.prices).last
219
+ upper = Array(vector.bb_upper).last
220
+ lower = Array(vector.bb_lower).last
221
+ middle = Array(vector.bb_middle).last if vector.respond_to?(:bb_middle)
222
+
223
+ assert_fact(:bollinger, {
224
+ price: current_price,
225
+ upper: upper,
226
+ lower: lower,
227
+ middle: middle,
228
+ position: bb_position(current_price, lower, upper)
229
+ })
230
+ end
231
+ end
232
+ end
233
+
234
+ # Determine final trading signal from asserted signal facts
235
+ def determine_signal
236
+ # Query for signal facts
237
+ buy_signals = query_facts(:signal, { action: :buy })
238
+ sell_signals = query_facts(:signal, { action: :sell })
239
+
240
+ # Count confidence levels
241
+ buy_confidence = calculate_confidence(buy_signals)
242
+ sell_confidence = calculate_confidence(sell_signals)
243
+
244
+ # Determine signal based on confidence
245
+ if buy_confidence > sell_confidence && buy_confidence >= 0.5
246
+ @last_signal = :buy
247
+ elsif sell_confidence > buy_confidence && sell_confidence >= 0.5
248
+ @last_signal = :sell
249
+ else
250
+ @last_signal = :hold
251
+ end
252
+
253
+ @last_signal
254
+ end
255
+
256
+ # Calculate aggregate confidence from multiple signals
257
+ def calculate_confidence(signals)
258
+ return 0.0 if signals.empty?
259
+
260
+ total_confidence = signals.sum do |fact|
261
+ case fact.attributes[:confidence]
262
+ when :high then 1.0
263
+ when :medium then 0.6
264
+ when :low then 0.3
265
+ else 0.5
266
+ end
267
+ end
268
+
269
+ total_confidence / signals.size.to_f
270
+ end
271
+
272
+ # Helper methods for fact classification
273
+
274
+ def rsi_level(value)
275
+ return :oversold if value < 30
276
+ return :overbought if value > 70
277
+ :neutral
278
+ end
279
+
280
+ def macd_crossover(macd_line, signal_line)
281
+ return :none if macd_line.size < 2 || signal_line.size < 2
282
+
283
+ curr_macd = macd_line.last
284
+ prev_macd = macd_line[-2]
285
+ curr_signal = signal_line.last
286
+ prev_signal = signal_line[-2]
287
+
288
+ if prev_macd <= prev_signal && curr_macd > curr_signal
289
+ :bullish
290
+ elsif prev_macd >= prev_signal && curr_macd < curr_signal
291
+ :bearish
292
+ else
293
+ :none
294
+ end
295
+ end
296
+
297
+ def price_trend(prices)
298
+ return :neutral if prices.size < 2
299
+
300
+ first_half = prices[0...prices.size/2]
301
+ second_half = prices[prices.size/2..-1]
302
+
303
+ avg_first = first_half.sum / first_half.size.to_f
304
+ avg_second = second_half.sum / second_half.size.to_f
305
+
306
+ if avg_second > avg_first * 1.02
307
+ :up
308
+ elsif avg_second < avg_first * 0.98
309
+ :down
310
+ else
311
+ :neutral
312
+ end
313
+ end
314
+
315
+ def trend_strength(prices)
316
+ return :weak if prices.size < 10
317
+
318
+ changes = prices.each_cons(2).map { |a, b| (b - a) / a.to_f }
319
+ avg_change = changes.sum / changes.size.to_f
320
+
321
+ return :strong if avg_change.abs > 0.02
322
+ return :moderate if avg_change.abs > 0.01
323
+ :weak
324
+ end
325
+
326
+ def sma_crossover_signal(short_sma, long_sma)
327
+ return :bullish if short_sma > long_sma
328
+ return :bearish if short_sma < long_sma
329
+ :neutral
330
+ end
331
+
332
+ def volume_level(current, average)
333
+ ratio = current / average.to_f
334
+ return :high if ratio > 1.5
335
+ return :low if ratio < 0.5
336
+ :normal
337
+ end
338
+
339
+ def stoch_zone(value)
340
+ return :oversold if value < 20
341
+ return :overbought if value > 80
342
+ :neutral
343
+ end
344
+
345
+ def stoch_crossover(k_values, d_values)
346
+ return :none if k_values.size < 2 || d_values.size < 2
347
+
348
+ curr_k = k_values.last
349
+ prev_k = k_values[-2]
350
+ curr_d = d_values.last
351
+ prev_d = d_values[-2]
352
+
353
+ if prev_k <= prev_d && curr_k > curr_d
354
+ :bullish
355
+ elsif prev_k >= prev_d && curr_k < curr_d
356
+ :bearish
357
+ else
358
+ :none
359
+ end
360
+ end
361
+
362
+ def bb_position(price, lower, upper)
363
+ return :below if price < lower
364
+ return :above if price > upper
365
+ :inside
366
+ end
367
+
368
+ # Load default trading rules
369
+ def load_default_rules
370
+ return if @default_rules_loaded
371
+
372
+ # Capture kb reference for use in perform blocks
373
+ kb = @kb
374
+
375
+ # Rule 1: Buy on RSI oversold in uptrend
376
+ add_rule :buy_oversold_uptrend do
377
+ on :rsi, { level: :oversold }
378
+ on :trend, { short_term: :up }
379
+ perform do
380
+ kb.assert(:signal, { action: :buy, confidence: :high, reason: :oversold_uptrend })
381
+ end
382
+ end
383
+
384
+ # Rule 2: Sell on RSI overbought in downtrend
385
+ add_rule :sell_overbought_downtrend do
386
+ on :rsi, { level: :overbought }
387
+ on :trend, { short_term: :down }
388
+ perform do
389
+ kb.assert(:signal, { action: :sell, confidence: :high, reason: :overbought_downtrend })
390
+ end
391
+ end
392
+
393
+ # Rule 3: Buy on bullish MACD crossover
394
+ add_rule :buy_macd_bullish do
395
+ on :macd, { crossover: :bullish }
396
+ on :trend, { medium_term: :up }
397
+ perform do
398
+ kb.assert(:signal, { action: :buy, confidence: :medium, reason: :macd_crossover })
399
+ end
400
+ end
401
+
402
+ # Rule 4: Sell on bearish MACD crossover
403
+ add_rule :sell_macd_bearish do
404
+ on :macd, { crossover: :bearish }
405
+ on :trend, { medium_term: :down }
406
+ perform do
407
+ kb.assert(:signal, { action: :sell, confidence: :medium, reason: :macd_crossover })
408
+ end
409
+ end
410
+
411
+ # Rule 5: Buy at lower Bollinger Band
412
+ add_rule :buy_bb_lower do
413
+ on :bollinger, { position: :below }
414
+ on :trend, { short_term: :up }
415
+ perform do
416
+ kb.assert(:signal, { action: :buy, confidence: :medium, reason: :bollinger_bounce })
417
+ end
418
+ end
419
+
420
+ # Rule 6: Sell at upper Bollinger Band
421
+ add_rule :sell_bb_upper do
422
+ on :bollinger, { position: :above }
423
+ on :trend, { short_term: :down }
424
+ perform do
425
+ kb.assert(:signal, { action: :sell, confidence: :medium, reason: :bollinger_resistance })
426
+ end
427
+ end
428
+
429
+ # Rule 7: Buy on stochastic oversold crossover
430
+ add_rule :buy_stoch_oversold do
431
+ on :stochastic, { zone: :oversold, crossover: :bullish }
432
+ on :volume, { level: :high }
433
+ perform do
434
+ kb.assert(:signal, { action: :buy, confidence: :high, reason: :stoch_oversold_volume })
435
+ end
436
+ end
437
+
438
+ # Rule 8: Sell on stochastic overbought crossover
439
+ add_rule :sell_stoch_overbought do
440
+ on :stochastic, { zone: :overbought, crossover: :bearish }
441
+ on :volume, { level: :high }
442
+ perform do
443
+ kb.assert(:signal, { action: :sell, confidence: :high, reason: :stoch_overbought_volume })
444
+ end
445
+ end
446
+
447
+ # Rule 9: SMA golden cross (buy)
448
+ add_rule :buy_golden_cross do
449
+ on :sma_crossover, { signal: :bullish }
450
+ on :volume, { level: :high }
451
+ perform do
452
+ kb.assert(:signal, { action: :buy, confidence: :high, reason: :golden_cross })
453
+ end
454
+ end
455
+
456
+ # Rule 10: SMA death cross (sell)
457
+ add_rule :sell_death_cross do
458
+ on :sma_crossover, { signal: :bearish }
459
+ on :volume, { level: :high }
460
+ perform do
461
+ kb.assert(:signal, { action: :sell, confidence: :high, reason: :death_cross })
462
+ end
463
+ end
464
+
465
+ @default_rules_loaded = true
466
+ self
467
+ end
468
+ end
469
+ end
470
+ end
@@ -0,0 +1,46 @@
1
+ # lib/sqa/strategy/macd.rb
2
+ # frozen_string_literal: true
3
+
4
+ # MACD (Moving Average Convergence Divergence) crossover strategy
5
+ # Buy when MACD line crosses above signal line (bullish)
6
+ # Sell when MACD line crosses below signal line (bearish)
7
+ #
8
+ class SQA::Strategy::MACD
9
+ def self.trade(vector)
10
+ return :hold unless vector.respond_to?(:prices) && vector.prices&.size >= 35
11
+
12
+ prices = vector.prices
13
+
14
+ # Calculate MACD using SQAI (returns macd, signal, histogram)
15
+ macd_line, signal_line, histogram = SQAI.macd(
16
+ prices,
17
+ fast_period: 12,
18
+ slow_period: 26,
19
+ signal_period: 9
20
+ )
21
+
22
+ return :hold if macd_line.nil? || signal_line.nil? || macd_line.size < 2
23
+
24
+ # Get current and previous values
25
+ current_macd = macd_line[-1]
26
+ current_signal = signal_line[-1]
27
+ prev_macd = macd_line[-2]
28
+ prev_signal = signal_line[-2]
29
+
30
+ # Bullish crossover: MACD crosses above signal
31
+ if prev_macd <= prev_signal && current_macd > current_signal
32
+ :buy
33
+
34
+ # Bearish crossover: MACD crosses below signal
35
+ elsif prev_macd >= prev_signal && current_macd < current_signal
36
+ :sell
37
+
38
+ # No crossover
39
+ else
40
+ :hold
41
+ end
42
+ rescue => e
43
+ warn "MACD strategy error: #{e.message}"
44
+ :hold
45
+ end
46
+ end
@@ -6,7 +6,7 @@ class SQA::Strategy::MP
6
6
  extend SQA::Strategy::Common
7
7
 
8
8
  def self.trade(vector)
9
- mp = vector.market_profile=:mixed,
9
+ mp = vector.market_profile || :mixed
10
10
 
11
11
  if :resistance == mp
12
12
  :sell
@@ -0,0 +1,60 @@
1
+ # lib/sqa/strategy/stochastic.rb
2
+ # frozen_string_literal: true
3
+
4
+ # Stochastic Oscillator crossover strategy
5
+ # Buy when %K crosses above %D in oversold territory (< 20)
6
+ # Sell when %K crosses below %D in overbought territory (> 80)
7
+ #
8
+ class SQA::Strategy::Stochastic
9
+ def self.trade(vector)
10
+ return :hold unless vector.respond_to?(:prices) && vector.prices&.size >= 14
11
+
12
+ prices = vector.prices
13
+
14
+ # We need high, low, close arrays for stochastic
15
+ # For simplicity, use prices as close, and approximate high/low from recent range
16
+ # In a real scenario, you'd get actual high/low from the stock data
17
+ high = prices.dup
18
+ low = prices.dup
19
+ close = prices
20
+
21
+ # Calculate Stochastic using SQAI
22
+ # Returns fastk and fastd (or slowk and slowd depending on the function)
23
+ stoch_k, stoch_d = SQAI.stoch(
24
+ high,
25
+ low,
26
+ close,
27
+ fastk_period: 14,
28
+ slowk_period: 3,
29
+ slowd_period: 3
30
+ )
31
+
32
+ return :hold if stoch_k.nil? || stoch_d.nil? || stoch_k.size < 2
33
+
34
+ # Get current and previous values
35
+ current_k = stoch_k[-1]
36
+ current_d = stoch_d[-1]
37
+ prev_k = stoch_k[-2]
38
+ prev_d = stoch_d[-2]
39
+
40
+ # Oversold threshold
41
+ oversold = 20.0
42
+ # Overbought threshold
43
+ overbought = 80.0
44
+
45
+ # Buy signal: %K crosses above %D in oversold territory
46
+ if current_k < oversold && prev_k <= prev_d && current_k > current_d
47
+ :buy
48
+
49
+ # Sell signal: %K crosses below %D in overbought territory
50
+ elsif current_k > overbought && prev_k >= prev_d && current_k < current_d
51
+ :sell
52
+
53
+ else
54
+ :hold
55
+ end
56
+ rescue => e
57
+ warn "Stochastic strategy error: #{e.message}"
58
+ :hold
59
+ end
60
+ end
@@ -0,0 +1,57 @@
1
+ # lib/sqa/strategy/volume_breakout.rb
2
+ # frozen_string_literal: true
3
+
4
+ # Volume Breakout strategy
5
+ # Buy when price breaks above resistance with high volume
6
+ # Sell when price breaks below support with high volume
7
+ #
8
+ class SQA::Strategy::VolumeBreakout
9
+ def self.trade(vector)
10
+ return :hold unless vector.respond_to?(:prices) &&
11
+ vector.respond_to?(:volumes) &&
12
+ vector.prices&.size >= 20 &&
13
+ vector.volumes&.size >= 20
14
+
15
+ prices = vector.prices
16
+ volumes = vector.volumes
17
+
18
+ # Calculate moving averages
19
+ sma_20 = SQAI.sma(prices, period: 20)
20
+ return :hold if sma_20.nil?
21
+
22
+ current_price = prices.last
23
+ prev_price = prices[-2]
24
+ current_volume = volumes.last
25
+
26
+ # Calculate average volume
27
+ avg_volume = volumes.last(20).sum / 20.0
28
+
29
+ # High volume threshold (1.5x average)
30
+ volume_threshold = avg_volume * 1.5
31
+
32
+ # Get recent high and low (resistance and support) from previous prices
33
+ # Exclude current price to allow breakout detection
34
+ lookback_prices = prices[...-1].last(20) # Last 20 prices excluding current
35
+ recent_high = lookback_prices.max
36
+ recent_low = lookback_prices.min
37
+
38
+ # Buy signal: price breaks above recent high with high volume
39
+ if current_price > recent_high &&
40
+ prev_price <= recent_high &&
41
+ current_volume > volume_threshold
42
+ :buy
43
+
44
+ # Sell signal: price breaks below recent low with high volume
45
+ elsif current_price < recent_low &&
46
+ prev_price >= recent_low &&
47
+ current_volume > volume_threshold
48
+ :sell
49
+
50
+ else
51
+ :hold
52
+ end
53
+ rescue => e
54
+ warn "VolumeBreakout strategy error: #{e.message}"
55
+ :hold
56
+ end
57
+ end
data/lib/sqa/strategy.rb CHANGED
@@ -62,4 +62,9 @@ require_relative 'strategy/mr'
62
62
  require_relative 'strategy/random'
63
63
  require_relative 'strategy/rsi'
64
64
  require_relative 'strategy/sma'
65
+ require_relative 'strategy/bollinger_bands'
66
+ require_relative 'strategy/macd'
67
+ require_relative 'strategy/stochastic'
68
+ require_relative 'strategy/volume_breakout'
69
+ require_relative 'strategy/kbs_strategy'
65
70