Class: SQA::Ensemble
- Inherits:
-
Object
- Object
- SQA::Ensemble
- Defined in:
- lib/sqa/ensemble.rb
Overview
Ensemble - Combine multiple trading strategies
Provides methods for:
-
Majority voting
-
Weighted voting based on past performance
-
Meta-learning (strategy selection)
-
Strategy rotation based on market conditions
-
Confidence-based aggregation
Instance Attribute Summary collapse
-
#performance_history ⇒ Object
Returns the value of attribute performance_history.
-
#strategies ⇒ Object
Returns the value of attribute strategies.
-
#weights ⇒ Object
Returns the value of attribute weights.
Class Method Summary collapse
-
.trade(vector) ⇒ Symbol
Make ensemble compatible with Backtest (acts like a strategy).
Instance Method Summary collapse
-
#backtest_comparison(stock, initial_capital: 10_000) ⇒ Hash
Backtest ensemble vs individual strategies.
-
#confidence_vote(vector) ⇒ Symbol
Confidence-based voting.
-
#initialize(strategies:, voting_method: :majority, weights: nil) ⇒ Ensemble
constructor
Initialize ensemble.
-
#majority_vote(vector) ⇒ Symbol
Majority voting.
-
#rotate(stock) ⇒ Class
Rotate strategies based on market conditions.
-
#select_strategy(market_regime:, volatility: :medium) ⇒ Class
Select best strategy for current market conditions.
-
#signal(vector) ⇒ Symbol
Generate ensemble signal.
-
#statistics ⇒ Hash
Get ensemble statistics.
-
#trade(vector) ⇒ Object
Instance method for compatibility.
-
#unanimous_vote(vector) ⇒ Symbol
Unanimous voting (all strategies must agree).
-
#update_confidence(strategy_class, correct) ⇒ Object
Update confidence score for strategy.
-
#update_weight(strategy_index, performance) ⇒ Object
Update strategy weights based on performance.
-
#weighted_vote(vector) ⇒ Symbol
Weighted voting based on strategy performance.
Constructor Details
#initialize(strategies:, voting_method: :majority, weights: nil) ⇒ Ensemble
Initialize ensemble
32 33 34 35 36 37 38 |
# File 'lib/sqa/ensemble.rb', line 32 def initialize(strategies:, voting_method: :majority, weights: nil) @strategies = strategies @voting_method = voting_method @weights = weights || Array.new(strategies.size, 1.0 / strategies.size) @performance_history = Hash.new { |h, k| h[k] = [] } @confidence_scores = Hash.new(0.5) end |
Instance Attribute Details
#performance_history ⇒ Object
Returns the value of attribute performance_history.
23 24 25 |
# File 'lib/sqa/ensemble.rb', line 23 def performance_history @performance_history end |
#strategies ⇒ Object
Returns the value of attribute strategies.
23 24 25 |
# File 'lib/sqa/ensemble.rb', line 23 def strategies @strategies end |
#weights ⇒ Object
Returns the value of attribute weights.
23 24 25 |
# File 'lib/sqa/ensemble.rb', line 23 def weights @weights end |
Class Method Details
.trade(vector) ⇒ Symbol
Make ensemble compatible with Backtest (acts like a strategy)
275 276 277 278 |
# File 'lib/sqa/ensemble.rb', line 275 def self.trade(vector) # This won't work for class method, use instance instead raise NotImplementedError, "Use ensemble instance, not class" end |
Instance Method Details
#backtest_comparison(stock, initial_capital: 10_000) ⇒ Hash
Backtest ensemble vs individual strategies
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/sqa/ensemble.rb', line 245 def backtest_comparison(stock, initial_capital: 10_000) results = {} # Backtest ensemble ensemble_backtest = SQA::Backtest.new( stock: stock, strategy: self, initial_capital: initial_capital ) results[:ensemble] = ensemble_backtest.run # Backtest each individual strategy @strategies.each do |strategy_class| individual_backtest = SQA::Backtest.new( stock: stock, strategy: strategy_class, initial_capital: initial_capital ) results[strategy_class.name] = individual_backtest.run end results end |
#confidence_vote(vector) ⇒ Symbol
Confidence-based voting
Weight votes by strategy confidence scores.
125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/sqa/ensemble.rb', line 125 def confidence_vote(vector) votes = collect_votes(vector) scores = { buy: 0.0, sell: 0.0, hold: 0.0 } votes.each_with_index do |vote, idx| strategy_class = @strategies[idx] confidence = @confidence_scores[strategy_class] || 0.5 scores[vote] += confidence end scores.max_by { |_signal, score| score }.first end |
#majority_vote(vector) ⇒ Symbol
Majority voting
67 68 69 70 71 72 73 74 75 76 |
# File 'lib/sqa/ensemble.rb', line 67 def majority_vote(vector) votes = collect_votes(vector) # Count votes vote_counts = { buy: 0, sell: 0, hold: 0 } votes.each { |v| vote_counts[v] += 1 } # Return signal with most votes vote_counts.max_by { |_signal, count| count }.first end |
#rotate(stock) ⇒ Class
Rotate strategies based on market conditions
213 214 215 216 217 218 219 220 |
# File 'lib/sqa/ensemble.rb', line 213 def rotate(stock) regime_data = SQA::MarketRegime.detect(stock) select_strategy( market_regime: regime_data[:type], volatility: regime_data[:volatility] ) end |
#select_strategy(market_regime:, volatility: :medium) ⇒ Class
Select best strategy for current market conditions
Meta-learning approach: choose the strategy most likely to succeed.
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/sqa/ensemble.rb', line 181 def select_strategy(market_regime:, volatility: :medium) # Strategy performance by market condition # This could be learned from historical data strategy_preferences = { bull: { low: SQA::Strategy::EMA, medium: SQA::Strategy::MACD, high: SQA::Strategy::Bollinger }, bear: { low: SQA::Strategy::RSI, medium: SQA::Strategy::RSI, high: SQA::Strategy::Bollinger }, sideways: { low: SQA::Strategy::MR, medium: SQA::Strategy::MR, high: SQA::Strategy::Bollinger } } # Return preferred strategy or fall back to best performer strategy_preferences.dig(market_regime, volatility) || best_performing_strategy end |
#signal(vector) ⇒ Symbol
Generate ensemble signal
46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/sqa/ensemble.rb', line 46 def signal(vector) case @voting_method when :majority majority_vote(vector) when :weighted weighted_vote(vector) when :unanimous unanimous_vote(vector) when :confidence confidence_vote(vector) else majority_vote(vector) end end |
#statistics ⇒ Hash
Get ensemble statistics
227 228 229 230 231 232 233 234 235 236 |
# File 'lib/sqa/ensemble.rb', line 227 def statistics { num_strategies: @strategies.size, weights: @weights, confidence_scores: @confidence_scores, best_strategy: best_performing_strategy, worst_strategy: worst_performing_strategy, performance_history: @performance_history } end |
#trade(vector) ⇒ Object
Instance method for compatibility
283 284 285 |
# File 'lib/sqa/ensemble.rb', line 283 def trade(vector) signal(vector) end |
#unanimous_vote(vector) ⇒ Symbol
Unanimous voting (all strategies must agree)
104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/sqa/ensemble.rb', line 104 def unanimous_vote(vector) votes = collect_votes(vector) # All must agree if votes.all? { |v| v == :buy } :buy elsif votes.all? { |v| v == :sell } :sell else :hold end end |
#update_confidence(strategy_class, correct) ⇒ Object
Update confidence score for strategy
160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/sqa/ensemble.rb', line 160 def update_confidence(strategy_class, correct) current = @confidence_scores[strategy_class] # Exponential moving average of correctness alpha = 0.1 @confidence_scores[strategy_class] = if correct current + alpha * (1.0 - current) else current - alpha * current end end |
#update_weight(strategy_index, performance) ⇒ Object
Update strategy weights based on performance
Adjust weights to favor better-performing strategies.
147 148 149 150 151 152 |
# File 'lib/sqa/ensemble.rb', line 147 def update_weight(strategy_index, performance) @performance_history[@strategies[strategy_index]] << performance # Recalculate weights based on recent performance recalculate_weights end |
#weighted_vote(vector) ⇒ Symbol
Weighted voting based on strategy performance
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/sqa/ensemble.rb', line 84 def weighted_vote(vector) votes = collect_votes(vector) # Weighted scores scores = { buy: 0.0, sell: 0.0, hold: 0.0 } votes.each_with_index do |vote, idx| scores[vote] += @weights[idx] end # Return signal with highest weighted score scores.max_by { |_signal, score| score }.first end |