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,191 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Using FPL (Future Period Loss/Profit) Analysis
5
+ #
6
+ # This example demonstrates how to use the FPOP utilities to analyze
7
+ # potential future price movements and filter trading opportunities by
8
+ # risk and directional bias.
9
+
10
+ require 'sqa'
11
+
12
+ SQA.init
13
+
14
+ puts "=" * 70
15
+ puts "FPL (Future Period Loss/Profit) Analysis Example"
16
+ puts "=" * 70
17
+ puts
18
+
19
+ # Example 1: Basic FPL Calculation
20
+ puts "\n" + "=" * 70
21
+ puts "Example 1: Basic FPL Calculation"
22
+ puts "=" * 70
23
+ puts
24
+
25
+ # Simple price series
26
+ prices = [100, 102, 98, 105, 110, 115, 120, 118, 125, 130]
27
+
28
+ puts "Price series: #{prices.inspect}"
29
+ puts
30
+
31
+ # Calculate FPL with 3-day future period
32
+ fpl_data = SQA::FPOP.fpl(prices, fpop: 3)
33
+
34
+ puts "FPL Analysis (fpop=3):"
35
+ puts "-" * 70
36
+ fpl_data.each_with_index do |(min_delta, max_delta), idx|
37
+ puts "Index #{idx} (Price: #{prices[idx]}): " \
38
+ "Min: #{min_delta.round(2)}%, Max: #{max_delta.round(2)}%"
39
+ end
40
+ puts
41
+
42
+ # Example 2: Detailed FPL Analysis with Risk Metrics
43
+ puts "\n" + "=" * 70
44
+ puts "Example 2: Detailed FPL Analysis with Risk Metrics"
45
+ puts "=" * 70
46
+ puts
47
+
48
+ analysis = SQA::FPOP.fpl_analysis(prices, fpop: 3)
49
+
50
+ puts "Detailed Analysis:"
51
+ puts "-" * 70
52
+ analysis.each_with_index do |result, idx|
53
+ puts "Index #{idx}: #{result[:interpretation]}"
54
+ puts " Direction: #{result[:direction]}"
55
+ puts " Magnitude: #{result[:magnitude].round(2)}%"
56
+ puts " Risk (Volatility): #{result[:risk].round(2)}%"
57
+ puts " Range: #{result[:min_delta].round(2)}% to #{result[:max_delta].round(2)}%"
58
+ puts
59
+ end
60
+
61
+ # Example 3: Filtering by Quality
62
+ puts "\n" + "=" * 70
63
+ puts "Example 3: Filtering High-Quality Opportunities"
64
+ puts "=" * 70
65
+ puts
66
+
67
+ # Find low-risk, high-magnitude, bullish opportunities
68
+ quality_indices = SQA::FPOP.filter_by_quality(
69
+ analysis,
70
+ min_magnitude: 5.0, # At least 5% average movement
71
+ max_risk: 10.0, # At most 10% volatility
72
+ directions: [:UP] # Only bullish movements
73
+ )
74
+
75
+ puts "High-quality bullish opportunities (magnitude ≥ 5%, risk ≤ 10%):"
76
+ puts "-" * 70
77
+ quality_indices.each do |idx|
78
+ result = analysis[idx]
79
+ puts "Index #{idx} (Price: #{prices[idx]}): #{result[:interpretation]}"
80
+ end
81
+ puts
82
+
83
+ # Example 4: Real Stock Data Analysis
84
+ puts "\n" + "=" * 70
85
+ puts "Example 4: Real Stock Data FPL Analysis"
86
+ puts "=" * 70
87
+ puts
88
+
89
+ if ENV['RUN_INTEGRATION_TESTS']
90
+ # Load real stock data
91
+ puts "Loading stock data for AAPL..."
92
+ stock = SQA::Stock.new(ticker: 'AAPL')
93
+ puts "Loaded #{stock.df.size} days of price history"
94
+ puts
95
+
96
+ # Use DataFrame convenience method
97
+ stock_fpl_analysis = stock.df.fpl_analysis(fpop: 14)
98
+
99
+ # Analyze recent data (last 10 points)
100
+ puts "FPL Analysis for last 10 trading days (fpop=14):"
101
+ puts "-" * 70
102
+ stock_fpl_analysis.last(10).each_with_index do |result, idx|
103
+ actual_idx = stock_fpl_analysis.size - 10 + idx
104
+ puts "Day #{actual_idx}: #{result[:interpretation]}"
105
+ end
106
+ puts
107
+
108
+ # Find all high-quality opportunities in entire history
109
+ quality_indices = SQA::FPOP.filter_by_quality(
110
+ stock_fpl_analysis,
111
+ min_magnitude: 8.0,
112
+ max_risk: 15.0,
113
+ directions: [:UP, :DOWN]
114
+ )
115
+
116
+ puts "Found #{quality_indices.size} high-quality opportunities"
117
+ puts " (magnitude ≥ 8%, risk ≤ 15%, clear direction)"
118
+ puts
119
+
120
+ # Distribution of directions
121
+ directions_dist = stock_fpl_analysis.map { |r| r[:direction] }.tally
122
+ puts "Direction distribution across all #{stock_fpl_analysis.size} points:"
123
+ directions_dist.each do |direction, count|
124
+ pct = (count.to_f / stock_fpl_analysis.size * 100).round(2)
125
+ puts " #{direction}: #{count} (#{pct}%)"
126
+ end
127
+ puts
128
+
129
+ # Risk-reward analysis
130
+ ratios = SQA::FPOP.risk_reward_ratios(stock_fpl_analysis)
131
+ avg_ratio = ratios.sum / ratios.size
132
+ puts "Average risk-reward ratio: #{avg_ratio.round(3)}"
133
+ else
134
+ puts "Skipping real stock analysis (set RUN_INTEGRATION_TESTS=1 to enable)"
135
+ end
136
+
137
+ # Example 5: Using FPL with Strategy Generator
138
+ puts "\n" + "=" * 70
139
+ puts "Example 5: FPL-Enhanced Strategy Generator"
140
+ puts "=" * 70
141
+ puts
142
+
143
+ if ENV['RUN_INTEGRATION_TESTS']
144
+ puts "Generating strategies with FPL quality filtering..."
145
+ puts
146
+
147
+ # Create generator with FPL quality filters
148
+ generator = SQA::StrategyGenerator.new(
149
+ stock: stock,
150
+ min_gain_percent: 10.0,
151
+ fpop: 10,
152
+ max_fpl_risk: 20.0, # NEW: Filter high-volatility points
153
+ required_fpl_directions: [:UP] # NEW: Only consider bullish patterns
154
+ )
155
+
156
+ patterns = generator.discover_patterns(min_pattern_frequency: 2)
157
+
158
+ if patterns.any?
159
+ puts "Discovered #{patterns.size} patterns from FPL-filtered opportunities"
160
+ puts
161
+
162
+ # Show quality metrics of discovered patterns
163
+ profitable_points = generator.profitable_points
164
+ if profitable_points.first.fpl_direction
165
+ avg_risk = profitable_points.map(&:fpl_risk).compact.sum / profitable_points.size
166
+ avg_magnitude = profitable_points.map(&:fpl_magnitude).compact.sum / profitable_points.size
167
+
168
+ puts "FPL Quality Metrics of Discovered Patterns:"
169
+ puts " Average risk: #{avg_risk.round(2)}%"
170
+ puts " Average magnitude: #{avg_magnitude.round(2)}%"
171
+ puts " All patterns are #{generator.required_fpl_directions.inspect} direction"
172
+ end
173
+ else
174
+ puts "No patterns found with current FPL filters"
175
+ puts "Try relaxing max_fpl_risk or removing direction requirements"
176
+ end
177
+ else
178
+ puts "Skipping FPL-enhanced strategy generation (set RUN_INTEGRATION_TESTS=1)"
179
+ end
180
+
181
+ puts
182
+ puts "=" * 70
183
+ puts "FPL Analysis Complete!"
184
+ puts "=" * 70
185
+ puts
186
+ puts "Key Takeaways:"
187
+ puts "- FPL shows potential upside (max_delta) and downside (min_delta)"
188
+ puts "- Risk metric measures volatility during the future period"
189
+ puts "- Direction classification helps identify clear trends vs uncertain markets"
190
+ puts "- Use quality filters to find high-probability, low-risk opportunities"
191
+ puts "- Integrate with Strategy Generator for risk-aware pattern discovery"
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Using Genetic Programming to Evolve Trading Strategy Parameters
5
+ #
6
+ # This example shows how to use SQA::GeneticProgram to automatically
7
+ # find optimal parameters for a trading strategy through evolution.
8
+
9
+ require 'sqa'
10
+
11
+ SQA.init
12
+
13
+ puts "=" * 60
14
+ puts "Genetic Programming Strategy Evolution"
15
+ puts "=" * 60
16
+ puts
17
+
18
+ # Load stock data
19
+ puts "Loading stock data for AAPL..."
20
+ stock = SQA::Stock.new(ticker: 'AAPL')
21
+ puts "Loaded #{stock.df.data.height} days of price history"
22
+ puts
23
+
24
+ # Define a simple RSI-based strategy factory
25
+ def create_rsi_strategy(period:, buy_threshold:, sell_threshold:)
26
+ Class.new do
27
+ define_singleton_method(:trade) do |vector|
28
+ return :hold unless vector.respond_to?(:prices) && vector.prices&.size >= period
29
+
30
+ # Calculate RSI with evolved period
31
+ prices = vector.prices
32
+ rsi = SQAI.rsi(prices, period: period)
33
+ current_rsi = rsi.last
34
+
35
+ # Use evolved thresholds
36
+ if current_rsi < buy_threshold
37
+ :buy
38
+ elsif current_rsi > sell_threshold
39
+ :sell
40
+ else
41
+ :hold
42
+ end
43
+ rescue => e
44
+ puts " Strategy error: #{e.message}"
45
+ :hold
46
+ end
47
+ end
48
+ end
49
+
50
+ # Create genetic program
51
+ gp = SQA::GeneticProgram.new(
52
+ stock: stock,
53
+ population_size: 20, # Small population for faster example
54
+ generations: 10, # Few generations for demo
55
+ mutation_rate: 0.15,
56
+ crossover_rate: 0.7
57
+ )
58
+
59
+ # Define gene constraints (parameter space to explore)
60
+ puts "Defining gene constraints..."
61
+ gp.define_genes(
62
+ period: (7..30).to_a, # RSI period: 7-30 days
63
+ buy_threshold: (20..40).to_a, # Buy when RSI below this
64
+ sell_threshold: (60..80).to_a # Sell when RSI above this
65
+ )
66
+ puts " RSI Period: 7-30"
67
+ puts " Buy Threshold: 20-40"
68
+ puts " Sell Threshold: 60-80"
69
+ puts
70
+
71
+ # Define fitness function (how to evaluate a strategy)
72
+ puts "Defining fitness function (backtest total return)..."
73
+ gp.fitness do |genes|
74
+ # Create strategy with these genes
75
+ strategy = create_rsi_strategy(
76
+ period: genes[:period],
77
+ buy_threshold: genes[:buy_threshold],
78
+ sell_threshold: genes[:sell_threshold]
79
+ )
80
+
81
+ # Backtest the strategy
82
+ backtest = SQA::Backtest.new(
83
+ stock: stock,
84
+ strategy: strategy,
85
+ initial_capital: 10_000.0,
86
+ commission: 1.0
87
+ )
88
+
89
+ results = backtest.run
90
+ results.total_return # Higher return = higher fitness
91
+ rescue => e
92
+ puts " Backtest failed for #{genes}: #{e.message}"
93
+ -100.0 # Poor fitness for failed backtests
94
+ end
95
+ puts
96
+
97
+ # Run evolution
98
+ puts "Starting evolution..."
99
+ puts "-" * 60
100
+ best = gp.evolve
101
+ puts "-" * 60
102
+ puts
103
+
104
+ # Display results
105
+ puts "Evolution Results:"
106
+ puts "=" * 60
107
+ puts "Best Parameters Found:"
108
+ puts " RSI Period: #{best.genes[:period]}"
109
+ puts " Buy Threshold: #{best.genes[:buy_threshold]}"
110
+ puts " Sell Threshold: #{best.genes[:sell_threshold]}"
111
+ puts " Fitness (Total Return): #{best.fitness.round(2)}%"
112
+ puts
113
+
114
+ # Show evolution history
115
+ puts "Evolution History:"
116
+ puts "-" * 60
117
+ gp.history.each do |gen|
118
+ puts "Generation #{gen[:generation]}: Best=#{gen[:best_fitness].round(2)}%, Avg=#{gen[:avg_fitness].round(2)}%"
119
+ end
120
+ puts
121
+
122
+ # Test the best strategy
123
+ puts "Testing Best Strategy:"
124
+ puts "-" * 60
125
+ best_strategy = create_rsi_strategy(
126
+ period: best.genes[:period],
127
+ buy_threshold: best.genes[:buy_threshold],
128
+ sell_threshold: best.genes[:sell_threshold]
129
+ )
130
+
131
+ backtest = SQA::Backtest.new(
132
+ stock: stock,
133
+ strategy: best_strategy,
134
+ initial_capital: 10_000.0,
135
+ commission: 1.0
136
+ )
137
+
138
+ results = backtest.run
139
+
140
+ puts "Backtest Results:"
141
+ puts " Total Return: #{results.total_return.round(2)}%"
142
+ puts " Sharpe Ratio: #{results.sharpe_ratio.round(2)}"
143
+ puts " Max Drawdown: #{results.max_drawdown.round(2)}%"
144
+ puts " Win Rate: #{results.win_rate.round(2)}%"
145
+ puts " Total Trades: #{results.total_trades}"
146
+ puts
147
+
148
+ puts "Example complete!"
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Using Knowledge-Based Strategy with RETE Forward Chaining
5
+ #
6
+ # This example shows how to use SQA::Strategy::KBS to create
7
+ # sophisticated rule-based trading systems.
8
+
9
+ require 'sqa'
10
+
11
+ SQA.init
12
+
13
+ puts "=" * 60
14
+ puts "Knowledge-Based Strategy (RETE) Example"
15
+ puts "=" * 60
16
+ puts
17
+
18
+ # Load stock data
19
+ puts "Loading stock data for AAPL..."
20
+ stock = SQA::Stock.new(ticker: 'AAPL')
21
+ puts "Loaded #{stock.df.data.height} days of price history"
22
+ puts
23
+
24
+ # Example 1: Using Default Rules
25
+ puts "\n" + "=" * 60
26
+ puts "Example 1: KBS with Default Rules"
27
+ puts "=" * 60
28
+ puts
29
+
30
+ strategy = SQA::Strategy::KBS.new(load_defaults: true)
31
+
32
+ puts "Loaded rules:"
33
+ strategy.print_rules
34
+ puts
35
+
36
+ # Prepare data vector
37
+ prices = stock.df["adj_close_price"].to_a
38
+ volumes = stock.df["volume"].to_a
39
+ highs = stock.df["high_price"].to_a
40
+ lows = stock.df["low_price"].to_a
41
+
42
+ require 'ostruct'
43
+ vector = OpenStruct.new(
44
+ prices: prices,
45
+ volumes: volumes,
46
+ highs: highs,
47
+ lows: lows,
48
+ rsi: SQAI.rsi(prices, period: 14),
49
+ macd: SQAI.macd(prices),
50
+ stoch_k: SQAI.stoch(highs, lows, prices).first,
51
+ stoch_d: SQAI.stoch(highs, lows, prices).last,
52
+ bb_upper: SQAI.bbands(prices).first,
53
+ bb_middle: SQAI.bbands(prices)[1],
54
+ bb_lower: SQAI.bbands(prices)[2]
55
+ )
56
+
57
+ signal = strategy.execute(vector)
58
+ puts "Generated Signal: #{signal.upcase}"
59
+ puts
60
+
61
+ # Show what facts were asserted
62
+ puts "Market Facts Asserted:"
63
+ strategy.print_facts
64
+ puts
65
+
66
+ # Example 2: Custom Rules
67
+ puts "\n" + "=" * 60
68
+ puts "Example 2: KBS with Custom Rules"
69
+ puts "=" * 60
70
+ puts
71
+
72
+ custom_strategy = SQA::Strategy::KBS.new(load_defaults: false)
73
+
74
+ # Add custom rule: Aggressive buy on multiple confirmations
75
+ custom_strategy.add_rule :aggressive_buy do
76
+ on :rsi, { level: :oversold }
77
+ on :stochastic, { zone: :oversold }
78
+ on :bollinger, { position: :below }
79
+ perform do
80
+ kb.assert(:signal, {
81
+ action: :buy,
82
+ confidence: :high,
83
+ reason: :triple_confirmation
84
+ })
85
+ end
86
+ end
87
+
88
+ # Add custom rule: Conservative sell
89
+ custom_strategy.add_rule :conservative_sell do
90
+ on :rsi, { level: :overbought }
91
+ on :trend, { short_term: :down }
92
+ perform do
93
+ kb.assert(:signal, {
94
+ action: :sell,
95
+ confidence: :medium,
96
+ reason: :overbought_downtrend
97
+ })
98
+ end
99
+ end
100
+
101
+ # Add custom rule: Volume-confirmed breakout
102
+ custom_strategy.add_rule :volume_breakout do
103
+ on :trend, { short_term: :up, strength: :strong }
104
+ on :volume, { level: :high }
105
+ perform do
106
+ kb.assert(:signal, {
107
+ action: :buy,
108
+ confidence: :high,
109
+ reason: :volume_breakout
110
+ })
111
+ end
112
+ end
113
+
114
+ puts "Custom rules defined:"
115
+ custom_strategy.print_rules
116
+ puts
117
+
118
+ signal = custom_strategy.execute(vector)
119
+ puts "Generated Signal: #{signal.upcase}"
120
+ puts
121
+
122
+ # Example 3: Backtesting KBS Strategy
123
+ puts "\n" + "=" * 60
124
+ puts "Example 3: Backtesting KBS Strategy"
125
+ puts "=" * 60
126
+ puts
127
+
128
+ backtest = SQA::Backtest.new(
129
+ stock: stock,
130
+ strategy: SQA::Strategy::KBS,
131
+ initial_capital: 10_000.0,
132
+ commission: 1.0
133
+ )
134
+
135
+ results = backtest.run
136
+
137
+ puts "Backtest Results:"
138
+ puts "-" * 60
139
+ puts "Total Return: #{results.total_return.round(2)}%"
140
+ puts "Annualized Return: #{results.annualized_return.round(2)}%"
141
+ puts "Sharpe Ratio: #{results.sharpe_ratio.round(2)}"
142
+ puts "Max Drawdown: #{results.max_drawdown.round(2)}%"
143
+ puts "Total Trades: #{results.total_trades}"
144
+ puts "Win Rate: #{results.win_rate.round(2)}%"
145
+ puts "Profit Factor: #{results.profit_factor.round(2)}"
146
+ puts
147
+
148
+ # Example 4: Interactive Rule Building
149
+ puts "\n" + "=" * 60
150
+ puts "Example 4: Interactive Rule Builder"
151
+ puts "=" * 60
152
+ puts
153
+
154
+ interactive_strategy = SQA::Strategy::KBS.new(load_defaults: false)
155
+
156
+ # Build complex multi-condition rules
157
+ interactive_strategy.add_rule :golden_opportunity do
158
+ desc "Perfect storm: Multiple bullish indicators align"
159
+
160
+ # Multiple conditions
161
+ on :rsi, { level: :oversold }
162
+ on :macd, { crossover: :bullish }
163
+ on :stochastic, { zone: :oversold, crossover: :bullish }
164
+ on :trend, { short_term: :up, strength: :strong }
165
+ on :volume, { level: :high }
166
+
167
+ # Negation: Don't buy if already overbought elsewhere
168
+ without :rsi, { level: :overbought }
169
+
170
+ # Action
171
+ perform do
172
+ kb.assert(:signal, {
173
+ action: :buy,
174
+ confidence: :high,
175
+ reason: :golden_opportunity,
176
+ strength: 10
177
+ })
178
+ puts " 🎯 Golden opportunity detected!"
179
+ end
180
+ end
181
+
182
+ interactive_strategy.add_rule :disaster_warning do
183
+ desc "Red flags: Multiple bearish indicators"
184
+
185
+ on :rsi, { level: :overbought }
186
+ on :macd, { crossover: :bearish }
187
+ on :trend, { short_term: :down }
188
+
189
+ perform do
190
+ kb.assert(:signal, {
191
+ action: :sell,
192
+ confidence: :high,
193
+ reason: :disaster_warning,
194
+ urgency: :high
195
+ })
196
+ puts " ⚠️ Disaster warning detected!"
197
+ end
198
+ end
199
+
200
+ puts "Interactive rules defined:"
201
+ interactive_strategy.print_rules
202
+ puts
203
+
204
+ signal = interactive_strategy.execute(vector)
205
+ puts "Final Signal: #{signal.upcase}"
206
+ puts
207
+
208
+ puts "Example complete!"