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.
- checksums.yaml +4 -4
- data/.goose/memory/development.txt +3 -0
- data/.semver +6 -0
- data/ARCHITECTURE.md +648 -0
- data/CHANGELOG.md +82 -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 +812 -262
- 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/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/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/data_frame/alpha_vantage.rb +43 -65
- data/lib/sqa/data_frame/data.rb +92 -0
- data/lib/sqa/data_frame/yahoo_finance.rb +35 -43
- 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 +15 -8
- 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/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 -125
- data/lib/sqa/strategy/bollinger_bands.rb +42 -0
- 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 +23 -16
- data/main.just +81 -0
- data/mkdocs.yml +288 -0
- data/trace.log +0 -0
- metadata +261 -51
- data/bin/sqa +0 -6
- data/lib/patches/dry-cli.rb +0 -228
- data/lib/sqa/activity.rb +0 -10
- data/lib/sqa/cli.rb +0 -62
- data/lib/sqa/commands/analysis.rb +0 -309
- data/lib/sqa/commands/base.rb +0 -139
- data/lib/sqa/commands/web.rb +0 -199
- data/lib/sqa/commands.rb +0 -22
- data/lib/sqa/constants.rb +0 -23
- data/lib/sqa/indicator/average_true_range.rb +0 -33
- 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
|
@@ -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!"
|