Class: SQA::SectorAnalyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/sqa/sector_analyzer.rb

Constant Summary collapse

SECTORS =
{
  technology: %w[AAPL MSFT GOOGL NVDA AMD INTC],
  finance: %w[JPM BAC GS MS C WFC],
  healthcare: %w[JNJ UNH PFE ABBV TMO MRK],
  energy: %w[XOM CVX COP SLB EOG MPC],
  consumer: %w[AMZN TSLA HD WMT NKE MCD],
  industrial: %w[CAT DE BA MMM HON UPS],
  materials: %w[LIN APD SHW FCX NEM DD],
  utilities: %w[NEE DUK SO D AEP EXC],
  real_estate: %w[AMT PLD CCI EQIX SPG O],
  communications: %w[META NFLX DIS CMCSA T VZ]
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(db_dir: '/tmp/sqa_sectors') ⇒ SectorAnalyzer

Returns a new instance of SectorAnalyzer.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/sqa/sector_analyzer.rb', line 42

def initialize(db_dir: '/tmp/sqa_sectors')
  @blackboards = {}
  @stocks_by_sector = Hash.new { |h, k| h[k] = [] }
  @db_dir = db_dir

  # Create directory for blackboard databases
  require 'fileutils'
  FileUtils.mkdir_p(@db_dir)

  # Initialize blackboard for each sector
  SECTORS.keys.each do |sector|
    init_sector_blackboard(sector)
  end
end

Instance Attribute Details

#blackboardsObject (readonly)

Returns the value of attribute blackboards.



40
41
42
# File 'lib/sqa/sector_analyzer.rb', line 40

def blackboards
  @blackboards
end

#stocks_by_sectorObject (readonly)

Returns the value of attribute stocks_by_sector.



40
41
42
# File 'lib/sqa/sector_analyzer.rb', line 40

def stocks_by_sector
  @stocks_by_sector
end

Instance Method Details

#add_stock(stock, sector:) ⇒ Object

Add a stock to sector analysis

Parameters:

  • stock (SQA::Stock, String)

    Stock object or ticker

  • sector (Symbol)

    Sector classification

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/sqa/sector_analyzer.rb', line 62

def add_stock(stock, sector:)
  raise ArgumentError, "Unknown sector: #{sector}" unless SECTORS.key?(sector)

  ticker = stock.is_a?(String) ? stock : stock.ticker
  @stocks_by_sector[sector] << ticker unless @stocks_by_sector[sector].include?(ticker)

  # Assert fact in sector blackboard
  kb = @blackboards[sector]
  kb.add_fact(:stock_registered, {
    ticker: ticker,
    sector: sector,
    registered_at: Time.now.to_i
  })

  ticker
end

#detect_sector_regime(sector, stocks) ⇒ Hash

Detect sector regime

Parameters:

  • sector (Symbol)

    Sector to analyze

  • stocks (Array<SQA::Stock>)

    Stock objects

Returns:

  • (Hash)

    Sector regime information

Raises:

  • (ArgumentError)


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/sqa/sector_analyzer.rb', line 146

def detect_sector_regime(sector, stocks)
  raise ArgumentError, "Unknown sector: #{sector}" unless SECTORS.key?(sector)

  kb = @blackboards[sector]

  # Detect regime for each stock
  regimes = stocks.map do |stock|
    SQA::MarketRegime.detect(stock)
  end

  # Determine consensus regime
  regime_counts = regimes.group_by { |r| r[:type] }.transform_values(&:size)
  consensus_regime = regime_counts.max_by { |_k, v| v }&.first

  # Calculate sector strength (% of stocks in bull regime)
  bull_count = regimes.count { |r| r[:type] == :bull }
  sector_strength = (bull_count.to_f / stocks.size * 100).round(2)

  result = {
    sector: sector,
    consensus_regime: consensus_regime,
    sector_strength: sector_strength,
    stock_regimes: regimes,
    detected_at: Time.now
  }

  # Assert sector regime fact
  kb.add_fact(:sector_regime, {
    sector: sector,
    regime: consensus_regime,
    strength: sector_strength,
    timestamp: Time.now.to_i
  })

  result
end

#discover_sector_patterns(sector, stocks, **options) ⇒ Array<Hash>

Discover patterns for an entire sector

Parameters:

  • sector (Symbol)

    Sector to analyze

  • stocks (Array<SQA::Stock>)

    Stock objects to analyze

  • options (Hash)

    Pattern discovery options

Returns:

  • (Array<Hash>)

    Sector-wide patterns

Raises:

  • (ArgumentError)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/sqa/sector_analyzer.rb', line 86

def discover_sector_patterns(sector, stocks, **options)
  raise ArgumentError, "Unknown sector: #{sector}" unless SECTORS.key?(sector)

  kb = @blackboards[sector]
  all_patterns = []

  debug_me {"Discovering patterns for #{sector.to_s.upcase} sector - #{stocks.size} stocks"}

  # Discover patterns for each stock
  stocks.each do |stock|
    debug_me {"Analyzing #{stock.ticker}..."}

    generator = SQA::StrategyGenerator.new(stock: stock, **options)
    patterns = generator.discover_patterns

    # Assert pattern facts in blackboard
    patterns.each_with_index do |pattern, i|
      kb.add_fact(:pattern_discovered, {
        ticker: stock.ticker,
        pattern_id: "#{stock.ticker}_#{i}",
        conditions: pattern.conditions,
        frequency: pattern.frequency,
        avg_gain: pattern.avg_gain,
        sector: sector
      })

      all_patterns << {
        ticker: stock.ticker,
        pattern: pattern,
        sector: sector
      }
    end
  end

  # Detect sector-wide patterns (patterns that appear in multiple stocks)
  sector_patterns = find_common_patterns(all_patterns)

  # Assert sector-wide patterns
  sector_patterns.each do |sp|
    kb.add_fact(:sector_pattern, {
      sector: sector,
      conditions: sp[:conditions],
      stock_count: sp[:stocks].size,
      stocks: sp[:stocks],
      avg_frequency: sp[:avg_frequency],
      avg_gain: sp[:avg_gain]
    })
  end

  debug_me {"Sector Analysis Complete - #{all_patterns.size} individual, #{sector_patterns.size} sector-wide patterns"}

  sector_patterns
end

Print sector summary

Parameters:

  • sector (Symbol)

    Sector to summarize



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/sqa/sector_analyzer.rb', line 202

def print_sector_summary(sector)
  kb = @blackboards[sector]

  puts "\n" + "=" * 70
  puts "#{sector.to_s.upcase} SECTOR SUMMARY"
  puts "=" * 70

  # Count facts by type
  fact_counts = kb.working_memory.facts.group_by(&:type).transform_values(&:size)

  puts "\nFacts in Blackboard:"
  fact_counts.each do |type, count|
    puts "  #{type}: #{count}"
  end

  # Show sector regime if available
  regime_facts = query_sector(sector, :sector_regime)
  if regime_facts.any?
    latest = regime_facts.last
    puts "\nCurrent Sector Regime:"
    puts "  Type: #{latest[:regime]}"
    puts "  Strength: #{latest[:strength]}%"
  end

  # Show sector patterns if available
  pattern_facts = query_sector(sector, :sector_pattern)
  if pattern_facts.any?
    puts "\nSector-Wide Patterns: #{pattern_facts.size}"
    pattern_facts.first(3).each_with_index do |fact, i|
      puts "  #{i + 1}. Conditions: #{fact[:conditions]}"
      puts "     Stocks: #{fact[:stocks].join(', ')}"
      puts "     Avg Gain: #{fact[:avg_gain].round(2)}%"
    end
  end

  puts "=" * 70
end

#query_sector(sector, fact_type, pattern = {}) ⇒ Array<KBS::Fact>

Query sector blackboard

Parameters:

  • sector (Symbol)

    Sector to query

  • fact_type (Symbol)

    Type of fact to query

  • pattern (Hash) (defaults to: {})

    Pattern to match

Returns:

  • (Array<KBS::Fact>)

    Matching facts



190
191
192
193
194
195
196
# File 'lib/sqa/sector_analyzer.rb', line 190

def query_sector(sector, fact_type, pattern = {})
  kb = @blackboards[sector]
  kb.working_memory.facts.select do |fact|
    next false unless fact.type == fact_type
    pattern.all? { |key, value| fact.attributes[key] == value }
  end
end