kbs 0.0.1

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/COMMITS.md +196 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +481 -0
  7. data/Rakefile +8 -0
  8. data/examples/README.md +531 -0
  9. data/examples/advanced_example.rb +270 -0
  10. data/examples/ai_enhanced_kbs.rb +523 -0
  11. data/examples/blackboard_demo.rb +50 -0
  12. data/examples/car_diagnostic.rb +64 -0
  13. data/examples/concurrent_inference_demo.rb +363 -0
  14. data/examples/csv_trading_system.rb +559 -0
  15. data/examples/iot_demo_using_dsl.rb +83 -0
  16. data/examples/portfolio_rebalancing_system.rb +651 -0
  17. data/examples/redis_trading_demo.rb +177 -0
  18. data/examples/sample_stock_data.csv +46 -0
  19. data/examples/stock_trading_advanced.rb +469 -0
  20. data/examples/stock_trading_system.rb.bak +563 -0
  21. data/examples/timestamped_trading.rb +286 -0
  22. data/examples/trading_demo.rb +334 -0
  23. data/examples/working_demo.rb +176 -0
  24. data/lib/kbs/alpha_memory.rb +37 -0
  25. data/lib/kbs/beta_memory.rb +57 -0
  26. data/lib/kbs/blackboard/audit_log.rb +115 -0
  27. data/lib/kbs/blackboard/engine.rb +83 -0
  28. data/lib/kbs/blackboard/fact.rb +65 -0
  29. data/lib/kbs/blackboard/memory.rb +191 -0
  30. data/lib/kbs/blackboard/message_queue.rb +96 -0
  31. data/lib/kbs/blackboard/persistence/hybrid_store.rb +118 -0
  32. data/lib/kbs/blackboard/persistence/redis_store.rb +218 -0
  33. data/lib/kbs/blackboard/persistence/sqlite_store.rb +242 -0
  34. data/lib/kbs/blackboard/persistence/store.rb +55 -0
  35. data/lib/kbs/blackboard/redis_audit_log.rb +107 -0
  36. data/lib/kbs/blackboard/redis_message_queue.rb +111 -0
  37. data/lib/kbs/blackboard.rb +23 -0
  38. data/lib/kbs/condition.rb +26 -0
  39. data/lib/kbs/dsl/condition_helpers.rb +57 -0
  40. data/lib/kbs/dsl/knowledge_base.rb +86 -0
  41. data/lib/kbs/dsl/pattern_evaluator.rb +69 -0
  42. data/lib/kbs/dsl/rule_builder.rb +115 -0
  43. data/lib/kbs/dsl/variable.rb +35 -0
  44. data/lib/kbs/dsl.rb +18 -0
  45. data/lib/kbs/fact.rb +43 -0
  46. data/lib/kbs/join_node.rb +117 -0
  47. data/lib/kbs/negation_node.rb +88 -0
  48. data/lib/kbs/production_node.rb +28 -0
  49. data/lib/kbs/rete_engine.rb +108 -0
  50. data/lib/kbs/rule.rb +46 -0
  51. data/lib/kbs/token.rb +37 -0
  52. data/lib/kbs/version.rb +5 -0
  53. data/lib/kbs/working_memory.rb +32 -0
  54. data/lib/kbs.rb +20 -0
  55. metadata +164 -0
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/kbs/blackboard'
4
+
5
+ puts "Redis-Backed High-Frequency Trading System"
6
+ puts "=" * 70
7
+
8
+ # Demo 1: Pure Redis Store (fast, in-memory)
9
+ puts "\n=== Pure Redis Store Demo ==="
10
+ puts "Fast in-memory fact storage with Redis"
11
+
12
+ begin
13
+ redis_store = KBS::Blackboard::Persistence::RedisStore.new(
14
+ url: 'redis://localhost:6379/1' # Use DB 1 for demo
15
+ )
16
+
17
+ # Clear previous demo data
18
+ redis_store.redis.flushdb
19
+ puts "(Cleared previous Redis data)"
20
+
21
+ engine = KBS::Blackboard::Engine.new(store: redis_store)
22
+
23
+ # Add trading rules
24
+ spread_rule = KBS::Rule.new('tight_spread_opportunity') do |r|
25
+ r.conditions << KBS::Condition.new(:market_price, { symbol: 'AAPL' })
26
+ r.conditions << KBS::Condition.new(:order, { symbol: 'AAPL', type: 'BUY' })
27
+ r.action = ->(facts) {
28
+ price = facts.find { |f| f.type == :market_price }
29
+ order = facts.find { |f| f.type == :order }
30
+ spread = 0.02 # Tight spread
31
+ puts "\n💹 TRADING SIGNAL: Tight spread opportunity for #{price[:symbol]}"
32
+ puts " Price: $#{price[:price]}, Spread: $#{spread}"
33
+ puts " Order: #{order[:type]} #{order[:quantity]} @ $#{order[:limit]}"
34
+ }
35
+ end
36
+
37
+ high_volume_rule = KBS::Rule.new('high_volume_alert') do |r|
38
+ r.conditions << KBS::Condition.new(:market_price, { volume: ->(v) { v > 800 } })
39
+ r.action = ->(facts) {
40
+ price = facts.first
41
+ puts "\n📊 HIGH VOLUME: #{price[:symbol]} trading at #{price[:volume]} shares"
42
+ }
43
+ end
44
+
45
+ engine.add_rule(spread_rule)
46
+ engine.add_rule(high_volume_rule)
47
+
48
+ puts "\nAdding high-frequency market data..."
49
+ price1 = engine.add_fact(:market_price, { symbol: "AAPL", price: 150.25, volume: 1000 })
50
+ price2 = engine.add_fact(:market_price, { symbol: "GOOGL", price: 2800.50, volume: 500 })
51
+ order = engine.add_fact(:order, { symbol: "AAPL", type: "BUY", quantity: 100, limit: 150.00 })
52
+
53
+ puts "Facts added with UUIDs:"
54
+ puts " AAPL Price: #{price1.uuid}"
55
+ puts " GOOGL Price: #{price2.uuid}"
56
+ puts " Order: #{order.uuid}"
57
+
58
+ puts "\nRunning inference engine..."
59
+ engine.run
60
+
61
+ puts "\nPosting trading messages..."
62
+ engine.post_message("MarketDataFeed", "prices", { symbol: "AAPL", bid: 150.24, ask: 150.26 }, priority: 10)
63
+ engine.post_message("OrderManager", "orders", { action: "fill", order_id: order.uuid }, priority: 5)
64
+
65
+ puts "\nConsuming highest priority message..."
66
+ message = engine.consume_message("prices", "TradingStrategy")
67
+ puts " Received: #{message[:content]}" if message
68
+
69
+ puts "\nQuerying market prices..."
70
+ prices = engine.blackboard.get_facts(:market_price)
71
+ puts " Found #{prices.size} price(s):"
72
+ prices.each { |p| puts " - #{p}" }
73
+
74
+ puts "\nRedis Statistics:"
75
+ stats = engine.stats
76
+ stats.each do |key, value|
77
+ puts " #{key.to_s.gsub('_', ' ').capitalize}: #{value}"
78
+ end
79
+
80
+ engine.blackboard.close
81
+
82
+ rescue Redis::CannotConnectError => e
83
+ puts "\n⚠️ Redis not available: #{e.message}"
84
+ puts " Please start Redis: redis-server"
85
+ puts " Or install Redis: brew install redis (macOS)"
86
+ end
87
+
88
+ # Demo 2: Hybrid Store (Redis + SQLite)
89
+ puts "\n\n=== Hybrid Store Demo ==="
90
+ puts "Redis for fast fact access + SQLite for durable audit trail"
91
+
92
+ begin
93
+ hybrid_store = KBS::Blackboard::Persistence::HybridStore.new(
94
+ redis_url: 'redis://localhost:6379/2', # Use DB 2 for demo
95
+ db_path: ':memory:' # In-memory SQLite for demo
96
+ )
97
+
98
+ # Clear previous demo data from Redis
99
+ hybrid_store.redis_store.redis.flushdb
100
+ puts "(Cleared previous Redis data)"
101
+
102
+ engine = KBS::Blackboard::Engine.new(store: hybrid_store)
103
+
104
+ # Add monitoring rules
105
+ temp_alert = KBS::Rule.new('temperature_alert') do |r|
106
+ r.conditions << KBS::Condition.new(:sensor, {
107
+ type: 'temperature',
108
+ value: ->(v) { v > 25 }
109
+ })
110
+ r.action = ->(facts) {
111
+ sensor = facts.first
112
+ puts "\n🌡️ TEMPERATURE ALERT: #{sensor[:location]} at #{sensor[:value]}°C (threshold: 25°C)"
113
+ }
114
+ end
115
+
116
+ cpu_warning = KBS::Rule.new('cpu_warning') do |r|
117
+ r.conditions << KBS::Condition.new(:sensor, {
118
+ type: 'cpu_usage',
119
+ value: ->(v) { v > 40 }
120
+ })
121
+ r.action = ->(facts) {
122
+ sensor = facts.first
123
+ puts "\n⚙️ CPU WARNING: #{sensor[:location]} at #{sensor[:value]}% (threshold: 40%)"
124
+ }
125
+ end
126
+
127
+ engine.add_rule(temp_alert)
128
+ engine.add_rule(cpu_warning)
129
+
130
+ puts "\nAdding facts (stored in Redis)..."
131
+ sensor1 = engine.add_fact(:sensor, { location: "trading_floor", type: "temperature", value: 22 })
132
+ sensor2 = engine.add_fact(:sensor, { location: "data_center", type: "cpu_usage", value: 45 })
133
+
134
+ puts "Facts added:"
135
+ puts " Sensor 1: #{sensor1.uuid}"
136
+ puts " Sensor 2: #{sensor2.uuid}"
137
+
138
+ puts "\nRunning inference engine..."
139
+ engine.run
140
+
141
+ puts "\nUpdating sensor value (audit logged to SQLite)..."
142
+ sensor1[:value] = 28
143
+
144
+ puts "\nRunning inference again after update..."
145
+ engine.run
146
+
147
+ puts "\nFact History from SQLite Audit Log:"
148
+ history = engine.blackboard.get_history(limit: 5)
149
+ history.each do |entry|
150
+ puts " [#{entry[:timestamp].strftime('%H:%M:%S')}] #{entry[:action]}: #{entry[:fact_type]}(#{entry[:attributes]})"
151
+ end
152
+
153
+ puts "\nHybrid Store Benefits:"
154
+ puts " ✓ Facts in Redis (fast reads/writes)"
155
+ puts " ✓ Messages in Redis (real-time messaging)"
156
+ puts " ✓ Audit trail in SQLite (durable, queryable)"
157
+ puts " ✓ Best of both worlds for production systems"
158
+
159
+ puts "\nStatistics:"
160
+ stats = engine.stats
161
+ stats.each do |key, value|
162
+ puts " #{key.to_s.gsub('_', ' ').capitalize}: #{value}"
163
+ end
164
+
165
+ engine.blackboard.close
166
+
167
+ rescue Redis::CannotConnectError => e
168
+ puts "\n⚠️ Redis not available: #{e.message}"
169
+ puts " Hybrid store requires Redis for fact storage"
170
+ end
171
+
172
+ puts "\n" + "=" * 70
173
+ puts "Demo complete!"
174
+ puts "\nComparison:"
175
+ puts " SQLite Store: Durable, transactional, embedded (no server needed)"
176
+ puts " Redis Store: Fast (100x), distributed, requires Redis server"
177
+ puts " Hybrid Store: Fast facts + durable audit (production recommended)"
@@ -0,0 +1,46 @@
1
+ Date,Symbol,Open,High,Low,Close,Volume
2
+ 2024-08-01,AAPL,190.50,192.30,189.80,191.75,45231000
3
+ 2024-08-02,AAPL,191.90,193.45,190.25,192.10,42891000
4
+ 2024-08-03,AAPL,192.20,194.80,191.55,194.25,51203000
5
+ 2024-08-04,AAPL,194.00,195.20,192.30,193.85,38756000
6
+ 2024-08-05,AAPL,193.50,194.75,191.80,192.45,44672000
7
+ 2024-08-06,AAPL,192.80,195.60,192.45,195.25,47938000
8
+ 2024-08-07,AAPL,195.50,197.80,194.90,197.35,52847000
9
+ 2024-08-08,AAPL,197.20,198.45,195.75,196.80,46291000
10
+ 2024-08-09,AAPL,196.50,197.90,194.80,195.60,41859000
11
+ 2024-08-10,AAPL,195.80,198.20,195.40,197.85,49562000
12
+ 2024-08-11,AAPL,198.00,200.15,197.25,199.90,55743000
13
+ 2024-08-12,AAPL,199.75,201.40,198.60,200.85,48392000
14
+ 2024-08-13,AAPL,200.90,202.75,199.85,202.30,53681000
15
+ 2024-08-14,AAPL,202.50,204.20,201.80,203.45,47256000
16
+ 2024-08-15,AAPL,203.20,205.80,202.90,205.15,51847000
17
+ 2024-08-01,GOOGL,160.20,162.50,159.80,161.90,28453000
18
+ 2024-08-02,GOOGL,162.10,163.75,160.95,162.85,26781000
19
+ 2024-08-03,GOOGL,162.90,165.40,162.20,164.75,31294000
20
+ 2024-08-04,GOOGL,164.50,166.80,163.85,165.90,29102000
21
+ 2024-08-05,GOOGL,165.70,167.20,164.50,166.45,27638000
22
+ 2024-08-06,GOOGL,166.80,169.30,166.20,168.75,33572000
23
+ 2024-08-07,GOOGL,168.90,171.45,168.30,170.90,35847000
24
+ 2024-08-08,GOOGL,170.75,172.60,169.85,171.25,32194000
25
+ 2024-08-09,GOOGL,171.50,173.20,170.40,172.80,30685000
26
+ 2024-08-10,GOOGL,172.60,174.95,172.10,174.30,34729000
27
+ 2024-08-11,GOOGL,174.50,176.80,173.95,176.20,37591000
28
+ 2024-08-12,GOOGL,176.40,178.20,175.80,177.65,33847000
29
+ 2024-08-13,GOOGL,177.80,180.15,177.25,179.45,36293000
30
+ 2024-08-14,GOOGL,179.60,181.90,178.95,180.75,34562000
31
+ 2024-08-15,GOOGL,180.90,183.40,180.30,182.85,38174000
32
+ 2024-08-01,TSLA,235.80,240.20,234.50,238.90,78392000
33
+ 2024-08-02,TSLA,239.20,242.75,237.80,241.50,82451000
34
+ 2024-08-03,TSLA,241.80,245.60,240.90,244.20,89637000
35
+ 2024-08-04,TSLA,244.50,247.30,242.80,245.75,75284000
36
+ 2024-08-05,TSLA,245.90,248.40,243.20,246.85,71956000
37
+ 2024-08-06,TSLA,247.20,251.80,246.50,250.30,86742000
38
+ 2024-08-07,TSLA,250.60,254.90,249.80,253.45,94587000
39
+ 2024-08-08,TSLA,253.70,256.20,251.40,254.90,81639000
40
+ 2024-08-09,TSLA,254.50,257.80,252.90,255.60,78294000
41
+ 2024-08-10,TSLA,255.90,259.40,254.70,258.75,85731000
42
+ 2024-08-11,TSLA,259.00,262.80,257.90,261.50,92846000
43
+ 2024-08-12,TSLA,261.80,264.50,260.20,263.25,87359000
44
+ 2024-08-13,TSLA,263.50,267.90,262.80,266.40,91582000
45
+ 2024-08-14,TSLA,266.70,270.20,265.50,268.85,88473000
46
+ 2024-08-15,TSLA,269.10,273.60,268.40,272.30,95627000
@@ -0,0 +1,469 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/kbs'
4
+
5
+ class AdvancedStockTradingSystem
6
+ def initialize
7
+ @engine = KBS::ReteEngine.new
8
+ @portfolio = {
9
+ cash: 100000,
10
+ positions: {},
11
+ total_value: 100000
12
+ }
13
+ @market_data = {}
14
+ setup_rules
15
+ end
16
+
17
+ def setup_rules
18
+ golden_cross = KBS::Rule.new(
19
+ "golden_cross",
20
+ conditions: [
21
+ KBS::Condition.new(:technical, {
22
+ indicator: "ma_crossover",
23
+ ma50: ->(v) { v > 0 },
24
+ ma200: ->(v) { v > 0 }
25
+ }),
26
+ KBS::Condition.new(:stock, {
27
+ volume: ->(v) { v > 1000000 }
28
+ }),
29
+ KBS::Condition.new(:position, { status: "open" }, negated: true)
30
+ ],
31
+ action: lambda do |facts, bindings|
32
+ tech = facts.find { |f| f.type == :technical }
33
+ stock = facts.find { |f| f.type == :stock }
34
+ if tech && stock && tech[:ma50] > tech[:ma200] && tech[:ma50_prev] <= tech[:ma200_prev]
35
+ puts "📈 GOLDEN CROSS: #{stock[:symbol]}"
36
+ puts " 50-MA: $#{tech[:ma50].round(2)}, 200-MA: $#{tech[:ma200].round(2)}"
37
+ puts " Volume: #{stock[:volume].to_s.reverse.scan(/\d{1,3}/).join(',').reverse}"
38
+ puts " ACTION: Strong BUY signal"
39
+ end
40
+ end,
41
+ priority: 15
42
+ )
43
+
44
+ momentum_breakout = KBS::Rule.new(
45
+ "momentum_breakout",
46
+ conditions: [
47
+ KBS::Condition.new(:stock, {
48
+ price_change: ->(v) { v > 3 },
49
+ volume_ratio: ->(v) { v > 1.5 },
50
+ rsi: ->(v) { v.between?(40, 70) }
51
+ }),
52
+ KBS::Condition.new(:market, { sentiment: ->(s) { ["bullish", "neutral"].include?(s) } })
53
+ ],
54
+ action: lambda do |facts, bindings|
55
+ stock = facts.find { |f| f.type == :stock }
56
+ puts "🚀 MOMENTUM BREAKOUT: #{stock[:symbol]}"
57
+ puts " Price Change: +#{stock[:price_change]}%"
58
+ puts " Volume Spike: #{stock[:volume_ratio]}x average"
59
+ puts " RSI: #{stock[:rsi]}"
60
+ puts " ACTION: Momentum BUY"
61
+ end,
62
+ priority: 12
63
+ )
64
+
65
+ oversold_bounce = KBS::Rule.new(
66
+ "oversold_bounce",
67
+ conditions: [
68
+ KBS::Condition.new(:stock, {
69
+ rsi: ->(v) { v < 30 },
70
+ price: ->(p) { p > 0 }
71
+ }),
72
+ KBS::Condition.new(:support, {
73
+ level: ->(l) { l > 0 }
74
+ })
75
+ ],
76
+ action: lambda do |facts, bindings|
77
+ stock = facts.find { |f| f.type == :stock }
78
+ support = facts.find { |f| f.type == :support }
79
+ if stock && support && stock[:price] >= support[:level] * 0.98
80
+ puts "🔄 OVERSOLD REVERSAL: #{stock[:symbol]}"
81
+ puts " RSI: #{stock[:rsi]} (oversold)"
82
+ puts " Price: $#{stock[:price]} near support $#{support[:level].round(2)}"
83
+ puts " ACTION: Reversal BUY opportunity"
84
+ end
85
+ end,
86
+ priority: 10
87
+ )
88
+
89
+ trailing_stop = KBS::Rule.new(
90
+ "trailing_stop",
91
+ conditions: [
92
+ KBS::Condition.new(:position, {
93
+ status: "open",
94
+ profit_pct: ->(p) { p > 5 }
95
+ }),
96
+ KBS::Condition.new(:stock, {
97
+ price: ->(p) { p > 0 }
98
+ })
99
+ ],
100
+ action: lambda do |facts, bindings|
101
+ position = facts.find { |f| f.type == :position }
102
+ stock = facts.find { |f| f.type == :stock }
103
+ if position && stock
104
+ trailing_stop = position[:high_water] * 0.95
105
+ if stock[:price] <= trailing_stop
106
+ puts "🛑 TRAILING STOP: #{position[:symbol]}"
107
+ puts " Entry: $#{position[:entry_price]}"
108
+ puts " Current: $#{stock[:price]}"
109
+ puts " Stop: $#{trailing_stop.round(2)}"
110
+ puts " Profit: #{position[:profit_pct].round(1)}%"
111
+ puts " ACTION: SELL to lock profits"
112
+ end
113
+ end
114
+ end,
115
+ priority: 18
116
+ )
117
+
118
+ position_sizing = KBS::Rule.new(
119
+ "position_sizing",
120
+ conditions: [
121
+ KBS::Condition.new(:signal, {
122
+ action: "buy",
123
+ confidence: ->(c) { c > 0.6 }
124
+ }),
125
+ KBS::Condition.new(:portfolio, {
126
+ cash: ->(c) { c > 1000 }
127
+ })
128
+ ],
129
+ action: lambda do |facts, bindings|
130
+ signal = facts.find { |f| f.type == :signal }
131
+ portfolio = facts.find { |f| f.type == :portfolio }
132
+ if signal && portfolio
133
+ kelly = (signal[:confidence] * signal[:expected_return] - (1 - signal[:confidence])) / signal[:expected_return]
134
+ position_size = portfolio[:cash] * [kelly * 0.25, 0.1].min
135
+ puts "📊 POSITION SIZING: #{signal[:symbol]}"
136
+ puts " Confidence: #{(signal[:confidence] * 100).round}%"
137
+ puts " Kelly %: #{(kelly * 100).round(1)}%"
138
+ puts " Suggested Size: $#{position_size.round(0)}"
139
+ end
140
+ end,
141
+ priority: 8
142
+ )
143
+
144
+ earnings_play = KBS::Rule.new(
145
+ "earnings_play",
146
+ conditions: [
147
+ KBS::Condition.new(:earnings, {
148
+ days_until: ->(d) { d.between?(1, 5) },
149
+ expected_move: ->(m) { m > 5 }
150
+ }),
151
+ KBS::Condition.new(:options, {
152
+ iv: ->(v) { v > 30 },
153
+ iv_rank: ->(r) { r > 50 }
154
+ })
155
+ ],
156
+ action: lambda do |facts, bindings|
157
+ earnings = facts.find { |f| f.type == :earnings }
158
+ options = facts.find { |f| f.type == :options }
159
+ puts "💰 EARNINGS PLAY: #{earnings[:symbol]}"
160
+ puts " Days to Earnings: #{earnings[:days_until]}"
161
+ puts " Expected Move: ±#{earnings[:expected_move]}%"
162
+ puts " IV: #{options[:iv]}% (Rank: #{options[:iv_rank]})"
163
+ puts " ACTION: Consider volatility strategy"
164
+ end,
165
+ priority: 11
166
+ )
167
+
168
+ sector_rotation = KBS::Rule.new(
169
+ "sector_rotation",
170
+ conditions: [
171
+ KBS::Condition.new(:sector, {
172
+ performance: ->(p) { p > 1.1 },
173
+ trend: "up"
174
+ }),
175
+ KBS::Condition.new(:position, {
176
+ sector: ->(s) { s != nil },
177
+ profit_pct: ->(p) { p < 2 }
178
+ })
179
+ ],
180
+ action: lambda do |facts, bindings|
181
+ strong_sector = facts.find { |f| f.type == :sector }
182
+ weak_position = facts.find { |f| f.type == :position }
183
+ if strong_sector && weak_position && strong_sector[:name] != weak_position[:sector]
184
+ puts "🔄 SECTOR ROTATION"
185
+ puts " From: #{weak_position[:sector]} (underperforming)"
186
+ puts " To: #{strong_sector[:name]} (RS: #{strong_sector[:performance]})"
187
+ puts " ACTION: Rotate portfolio allocation"
188
+ end
189
+ end,
190
+ priority: 7
191
+ )
192
+
193
+ risk_alert = KBS::Rule.new(
194
+ "risk_concentration",
195
+ conditions: [
196
+ KBS::Condition.new(:portfolio_metrics, {
197
+ concentration: ->(c) { c > 0.3 }
198
+ }),
199
+ KBS::Condition.new(:market, {
200
+ volatility: ->(v) { v > 25 }
201
+ })
202
+ ],
203
+ action: lambda do |facts, bindings|
204
+ metrics = facts.find { |f| f.type == :portfolio_metrics }
205
+ puts "⚠️ RISK CONCENTRATION ALERT"
206
+ puts " Top Position: #{(metrics[:concentration] * 100).round}% of portfolio"
207
+ puts " Market Volatility: Elevated"
208
+ puts " ACTION: Reduce position sizes for risk management"
209
+ end,
210
+ priority: 16
211
+ )
212
+
213
+ vwap_reversion = KBS::Rule.new(
214
+ "vwap_reversion",
215
+ conditions: [
216
+ KBS::Condition.new(:intraday, {
217
+ distance_from_vwap: ->(d) { d.abs > 2 }
218
+ })
219
+ ],
220
+ action: lambda do |facts, bindings|
221
+ intraday = facts.find { |f| f.type == :intraday }
222
+ direction = intraday[:distance_from_vwap] > 0 ? "OVERBOUGHT" : "OVERSOLD"
223
+ puts "📊 VWAP REVERSION: #{intraday[:symbol]}"
224
+ puts " Status: #{direction}"
225
+ puts " Distance: #{intraday[:distance_from_vwap].round(1)} std devs"
226
+ puts " Current: $#{intraday[:price]}"
227
+ puts " VWAP: $#{intraday[:vwap].round(2)}"
228
+ puts " ACTION: Mean reversion trade"
229
+ end,
230
+ priority: 9
231
+ )
232
+
233
+ news_sentiment = KBS::Rule.new(
234
+ "news_sentiment",
235
+ conditions: [
236
+ KBS::Condition.new(:news, {
237
+ sentiment: ->(s) { s.abs > 0.7 },
238
+ volume: ->(v) { v > 10 }
239
+ }),
240
+ KBS::Condition.new(:stock, {
241
+ price_change: ->(p) { p.abs < 2 }
242
+ })
243
+ ],
244
+ action: lambda do |facts, bindings|
245
+ news = facts.find { |f| f.type == :news }
246
+ stock = facts.find { |f| f.type == :stock }
247
+ sentiment = news[:sentiment] > 0 ? "POSITIVE" : "NEGATIVE"
248
+ action = news[:sentiment] > 0 ? "BUY" : "SELL"
249
+ puts "📰 NEWS CATALYST: #{stock[:symbol]}"
250
+ puts " Sentiment: #{sentiment} (#{news[:sentiment].round(2)})"
251
+ puts " News Volume: #{news[:volume]} articles"
252
+ puts " Price Reaction: #{stock[:price_change].round(1)}% (lagging)"
253
+ puts " ACTION: #{action} on sentiment divergence"
254
+ end,
255
+ priority: 13
256
+ )
257
+
258
+ correlation_hedge = KBS::Rule.new(
259
+ "correlation_warning",
260
+ conditions: [
261
+ KBS::Condition.new(:correlation, {
262
+ value: ->(v) { v > 0.8 }
263
+ })
264
+ ],
265
+ action: lambda do |facts, bindings|
266
+ corr = facts.find { |f| f.type == :correlation }
267
+ puts "⚠️ HIGH CORRELATION"
268
+ puts " Pairs: #{corr[:symbol1]} <-> #{corr[:symbol2]}"
269
+ puts " Correlation: #{corr[:value].round(2)}"
270
+ puts " ACTION: Diversify or hedge positions"
271
+ end,
272
+ priority: 6
273
+ )
274
+
275
+ gap_fade = KBS::Rule.new(
276
+ "gap_fade",
277
+ conditions: [
278
+ KBS::Condition.new(:gap, {
279
+ size: ->(s) { s.abs > 2 }
280
+ }),
281
+ KBS::Condition.new(:stock, {
282
+ atr: ->(a) { a > 0 }
283
+ })
284
+ ],
285
+ action: lambda do |facts, bindings|
286
+ gap = facts.find { |f| f.type == :gap }
287
+ stock = facts.find { |f| f.type == :stock }
288
+ if gap && stock
289
+ gap_multiple = gap[:size].abs / (stock[:atr] / stock[:price] * 100)
290
+ if gap_multiple > 2
291
+ direction = gap[:size] > 0 ? "SHORT" : "LONG"
292
+ puts "📉 GAP FADE: #{stock[:symbol]}"
293
+ puts " Gap: #{gap[:size].round(1)}%"
294
+ puts " ATR Multiple: #{gap_multiple.round(1)}x"
295
+ puts " ACTION: #{direction} fade trade"
296
+ end
297
+ end
298
+ end,
299
+ priority: 8
300
+ )
301
+
302
+ @engine.add_rule(golden_cross)
303
+ @engine.add_rule(momentum_breakout)
304
+ @engine.add_rule(oversold_bounce)
305
+ @engine.add_rule(trailing_stop)
306
+ @engine.add_rule(position_sizing)
307
+ @engine.add_rule(earnings_play)
308
+ @engine.add_rule(sector_rotation)
309
+ @engine.add_rule(risk_alert)
310
+ @engine.add_rule(vwap_reversion)
311
+ @engine.add_rule(news_sentiment)
312
+ @engine.add_rule(correlation_hedge)
313
+ @engine.add_rule(gap_fade)
314
+ end
315
+
316
+ def simulate_market_tick(symbols)
317
+ symbols.each do |symbol|
318
+ @market_data[symbol] ||= {
319
+ price: 100 + rand(50),
320
+ ma50: 100,
321
+ ma200: 100,
322
+ volume: 1000000,
323
+ rsi: 50,
324
+ high_water: 100
325
+ }
326
+
327
+ data = @market_data[symbol]
328
+
329
+ price_change = rand(-5.0..5.0)
330
+ data[:price] = (data[:price] * (1 + price_change / 100)).round(2)
331
+ data[:ma50] = (data[:ma50] * 0.98 + data[:price] * 0.02).round(2)
332
+ data[:ma200] = (data[:ma200] * 0.995 + data[:price] * 0.005).round(2)
333
+ data[:volume] = (1000000 * (0.5 + rand * 2)).to_i
334
+ data[:rsi] = [[data[:rsi] + rand(-10..10), 0].max, 100].min
335
+ data[:high_water] = [data[:high_water], data[:price]].max
336
+
337
+ @engine.add_fact(:stock, {
338
+ symbol: symbol,
339
+ price: data[:price],
340
+ volume: data[:volume],
341
+ rsi: data[:rsi],
342
+ price_change: price_change,
343
+ volume_ratio: data[:volume] / 1000000.0,
344
+ atr: rand(1.0..3.0)
345
+ })
346
+
347
+ @engine.add_fact(:technical, {
348
+ symbol: symbol,
349
+ indicator: "ma_crossover",
350
+ ma50: data[:ma50],
351
+ ma200: data[:ma200],
352
+ ma50_prev: data[:ma50] - rand(-1.0..1.0),
353
+ ma200_prev: data[:ma200] - rand(-0.5..0.5)
354
+ })
355
+
356
+ @engine.add_fact(:support, {
357
+ symbol: symbol,
358
+ level: data[:price] * 0.95
359
+ })
360
+
361
+ if rand > 0.7
362
+ @engine.add_fact(:intraday, {
363
+ symbol: symbol,
364
+ price: data[:price],
365
+ vwap: data[:price] + rand(-2.0..2.0),
366
+ distance_from_vwap: rand(-3.0..3.0)
367
+ })
368
+ end
369
+
370
+ if rand > 0.8
371
+ @engine.add_fact(:news, {
372
+ symbol: symbol,
373
+ sentiment: rand(-1.0..1.0),
374
+ volume: rand(5..50)
375
+ })
376
+ end
377
+
378
+ if rand > 0.85
379
+ @engine.add_fact(:earnings, {
380
+ symbol: symbol,
381
+ days_until: rand(1..10),
382
+ expected_move: rand(3.0..15.0)
383
+ })
384
+
385
+ @engine.add_fact(:options, {
386
+ symbol: symbol,
387
+ iv: rand(20..80),
388
+ iv_rank: rand(0..100)
389
+ })
390
+ end
391
+
392
+ if rand > 0.9
393
+ @engine.add_fact(:gap, {
394
+ symbol: symbol,
395
+ size: rand(-5.0..5.0)
396
+ })
397
+ end
398
+ end
399
+
400
+ @engine.add_fact(:market, {
401
+ sentiment: ["bullish", "neutral", "bearish"].sample,
402
+ volatility: rand(10..40)
403
+ })
404
+
405
+ if @portfolio[:positions].any?
406
+ total_value = @portfolio[:cash] + @portfolio[:positions].values.sum { |p| p[:value] }
407
+ max_position = @portfolio[:positions].values.map { |p| p[:value] }.max || 0
408
+ @engine.add_fact(:portfolio_metrics, {
409
+ concentration: max_position.to_f / total_value
410
+ })
411
+ end
412
+
413
+ @engine.add_fact(:portfolio, {
414
+ cash: @portfolio[:cash],
415
+ risk_tolerance: 0.5
416
+ })
417
+
418
+ if rand > 0.85
419
+ symbols_pair = symbols.sample(2)
420
+ @engine.add_fact(:correlation, {
421
+ symbol1: symbols_pair[0],
422
+ symbol2: symbols_pair[1],
423
+ value: rand(0.5..0.95)
424
+ })
425
+ end
426
+
427
+ sectors = ["Technology", "Healthcare", "Finance", "Energy", "Consumer"]
428
+ @engine.add_fact(:sector, {
429
+ name: sectors.sample,
430
+ performance: rand(0.8..1.3),
431
+ trend: ["up", "down", "sideways"].sample
432
+ })
433
+ end
434
+
435
+ def run_simulation(symbols: ["AAPL", "GOOGL", "MSFT", "NVDA"], iterations: 10)
436
+ puts "\n" + "=" * 80
437
+ puts "ADVANCED STOCK TRADING SYSTEM"
438
+ puts "=" * 80
439
+ puts "Initial Capital: $100,000"
440
+ puts "Symbols: #{symbols.join(', ')}"
441
+ puts "Rules: #{@engine.rules.size} active trading strategies"
442
+ puts "=" * 80
443
+
444
+ iterations.times do |i|
445
+ puts "\n⏰ MARKET TICK #{i + 1} - #{Time.now.strftime('%H:%M:%S')}"
446
+ puts "-" * 60
447
+
448
+ @engine.working_memory.facts.clear
449
+
450
+ simulate_market_tick(symbols)
451
+
452
+ @engine.run
453
+
454
+ sleep(0.3) if i < iterations - 1
455
+ end
456
+
457
+ puts "\n" + "=" * 80
458
+ puts "SIMULATION COMPLETE"
459
+ puts "=" * 80
460
+ end
461
+ end
462
+
463
+ if __FILE__ == $0
464
+ system = AdvancedStockTradingSystem.new
465
+ system.run_simulation(
466
+ symbols: ["AAPL", "GOOGL", "MSFT", "NVDA", "TSLA", "META", "AMZN"],
467
+ iterations: 12
468
+ )
469
+ end