kbs 0.0.1 → 0.2.0

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +52 -0
  3. data/CHANGELOG.md +68 -2
  4. data/README.md +291 -362
  5. data/docs/advanced/custom-persistence.md +775 -0
  6. data/docs/advanced/debugging.md +726 -0
  7. data/docs/advanced/index.md +8 -0
  8. data/docs/advanced/performance.md +865 -0
  9. data/docs/advanced/testing.md +827 -0
  10. data/docs/api/blackboard.md +1157 -0
  11. data/docs/api/engine.md +1047 -0
  12. data/docs/api/facts.md +1212 -0
  13. data/docs/api/index.md +12 -0
  14. data/docs/api/rules.md +1104 -0
  15. data/docs/architecture/blackboard.md +544 -0
  16. data/docs/architecture/index.md +277 -0
  17. data/docs/architecture/network-structure.md +343 -0
  18. data/docs/architecture/rete-algorithm.md +737 -0
  19. data/docs/assets/css/custom.css +83 -0
  20. data/docs/assets/images/blackboard-architecture.svg +136 -0
  21. data/docs/assets/images/compiled-network.svg +101 -0
  22. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  23. data/docs/assets/images/fact-rule-relationship.svg +65 -0
  24. data/docs/assets/images/fact-structure.svg +42 -0
  25. data/docs/assets/images/inference-cycle.svg +47 -0
  26. data/docs/assets/images/kb-components.svg +43 -0
  27. data/docs/assets/images/kbs.jpg +0 -0
  28. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  29. data/docs/assets/images/rete-network-layers.svg +96 -0
  30. data/docs/assets/images/rule-structure.svg +44 -0
  31. data/docs/assets/images/system-layers.svg +69 -0
  32. data/docs/assets/images/trading-signal-network.svg +139 -0
  33. data/docs/assets/js/mathjax.js +17 -0
  34. data/docs/examples/index.md +223 -0
  35. data/docs/guides/blackboard-memory.md +589 -0
  36. data/docs/guides/dsl.md +1321 -0
  37. data/docs/guides/facts.md +652 -0
  38. data/docs/guides/getting-started.md +385 -0
  39. data/docs/guides/index.md +23 -0
  40. data/docs/guides/negation.md +529 -0
  41. data/docs/guides/pattern-matching.md +561 -0
  42. data/docs/guides/persistence.md +451 -0
  43. data/docs/guides/variable-binding.md +491 -0
  44. data/docs/guides/writing-rules.md +914 -0
  45. data/docs/index.md +155 -0
  46. data/docs/installation.md +156 -0
  47. data/docs/quick-start.md +221 -0
  48. data/docs/what-is-a-fact.md +694 -0
  49. data/docs/what-is-a-knowledge-base.md +350 -0
  50. data/docs/what-is-a-rule.md +833 -0
  51. data/examples/.gitignore +1 -0
  52. data/examples/README.md +2 -2
  53. data/examples/advanced_example.rb +2 -2
  54. data/examples/advanced_example_dsl.rb +224 -0
  55. data/examples/ai_enhanced_kbs.rb +1 -1
  56. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  57. data/examples/blackboard_demo_dsl.rb +50 -0
  58. data/examples/car_diagnostic.rb +1 -1
  59. data/examples/car_diagnostic_dsl.rb +54 -0
  60. data/examples/concurrent_inference_demo.rb +5 -6
  61. data/examples/concurrent_inference_demo_dsl.rb +362 -0
  62. data/examples/csv_trading_system.rb +1 -1
  63. data/examples/csv_trading_system_dsl.rb +525 -0
  64. data/examples/iot_demo_using_dsl.rb +1 -1
  65. data/examples/portfolio_rebalancing_system.rb +2 -2
  66. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  67. data/examples/redis_trading_demo_dsl.rb +177 -0
  68. data/examples/rule_source_demo.rb +123 -0
  69. data/examples/run_all.rb +50 -0
  70. data/examples/run_all_dsl.rb +49 -0
  71. data/examples/stock_trading_advanced.rb +1 -1
  72. data/examples/stock_trading_advanced_dsl.rb +404 -0
  73. data/examples/temp_dsl.txt +9392 -0
  74. data/examples/timestamped_trading.rb +1 -1
  75. data/examples/timestamped_trading_dsl.rb +258 -0
  76. data/examples/trading_demo.rb +1 -1
  77. data/examples/trading_demo_dsl.rb +322 -0
  78. data/examples/working_demo.rb +1 -1
  79. data/examples/working_demo_dsl.rb +160 -0
  80. data/lib/kbs/blackboard/engine.rb +3 -3
  81. data/lib/kbs/blackboard/fact.rb +1 -1
  82. data/lib/kbs/condition.rb +1 -1
  83. data/lib/kbs/decompiler.rb +204 -0
  84. data/lib/kbs/dsl/knowledge_base.rb +101 -2
  85. data/lib/kbs/dsl/variable.rb +1 -1
  86. data/lib/kbs/dsl.rb +3 -1
  87. data/lib/kbs/{rete_engine.rb → engine.rb} +42 -1
  88. data/lib/kbs/fact.rb +1 -1
  89. data/lib/kbs/version.rb +1 -1
  90. data/lib/kbs.rb +15 -13
  91. data/mkdocs.yml +181 -0
  92. metadata +74 -9
  93. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/kbs'
4
+
5
+ class AdvancedStockTradingSystem
6
+ include KBS::DSL::ConditionHelpers
7
+
8
+ def initialize
9
+ @kb = nil
10
+ @portfolio = {
11
+ cash: 100000,
12
+ positions: {},
13
+ total_value: 100000
14
+ }
15
+ @market_data = {}
16
+ setup_rules
17
+ end
18
+
19
+ def setup_rules
20
+ @kb = KBS.knowledge_base do
21
+ rule "golden_cross" do
22
+ priority 15
23
+ on :technical,
24
+ indicator: "ma_crossover",
25
+ ma50: satisfies { |v| v > 0 },
26
+ ma200: satisfies { |v| v > 0 }
27
+ on :stock, volume: satisfies { |v| v > 1000000 }
28
+ without.on :position, status: "open"
29
+
30
+ perform do |facts|
31
+ tech = facts.find { |f| f.type == :technical }
32
+ stock = facts.find { |f| f.type == :stock }
33
+ if tech && stock && tech[:ma50] > tech[:ma200] && tech[:ma50_prev] <= tech[:ma200_prev]
34
+ puts "📈 GOLDEN CROSS: #{stock[:symbol]}"
35
+ puts " 50-MA: $#{tech[:ma50].round(2)}, 200-MA: $#{tech[:ma200].round(2)}"
36
+ puts " Volume: #{stock[:volume].to_s.reverse.scan(/\d{1,3}/).join(',').reverse}"
37
+ puts " ACTION: Strong BUY signal"
38
+ end
39
+ end
40
+ end
41
+
42
+ rule "momentum_breakout" do
43
+ priority 12
44
+ on :stock,
45
+ price_change: satisfies { |v| v > 3 },
46
+ volume_ratio: satisfies { |v| v > 1.5 },
47
+ rsi: satisfies { |v| v.between?(40, 70) }
48
+ on :market, sentiment: satisfies { |s| ["bullish", "neutral"].include?(s) }
49
+
50
+ perform do |facts|
51
+ stock = facts.find { |f| f.type == :stock }
52
+ puts "🚀 MOMENTUM BREAKOUT: #{stock[:symbol]}"
53
+ puts " Price Change: +#{stock[:price_change]}%"
54
+ puts " Volume Spike: #{stock[:volume_ratio]}x average"
55
+ puts " RSI: #{stock[:rsi]}"
56
+ puts " ACTION: Momentum BUY"
57
+ end
58
+ end
59
+
60
+ rule "oversold_bounce" do
61
+ priority 10
62
+ on :stock,
63
+ rsi: satisfies { |v| v < 30 },
64
+ price: satisfies { |p| p > 0 }
65
+ on :support, level: satisfies { |l| l > 0 }
66
+
67
+ perform do |facts|
68
+ stock = facts.find { |f| f.type == :stock }
69
+ support = facts.find { |f| f.type == :support }
70
+ if stock && support && stock[:price] >= support[:level] * 0.98
71
+ puts "🔄 OVERSOLD REVERSAL: #{stock[:symbol]}"
72
+ puts " RSI: #{stock[:rsi]} (oversold)"
73
+ puts " Price: $#{stock[:price]} near support $#{support[:level].round(2)}"
74
+ puts " ACTION: Reversal BUY opportunity"
75
+ end
76
+ end
77
+ end
78
+
79
+ rule "trailing_stop" do
80
+ priority 18
81
+ on :position,
82
+ status: "open",
83
+ profit_pct: satisfies { |p| p > 5 }
84
+ on :stock, price: satisfies { |p| p > 0 }
85
+
86
+ perform do |facts|
87
+ position = facts.find { |f| f.type == :position }
88
+ stock = facts.find { |f| f.type == :stock }
89
+ if position && stock
90
+ trailing_stop = position[:high_water] * 0.95
91
+ if stock[:price] <= trailing_stop
92
+ puts "🛑 TRAILING STOP: #{position[:symbol]}"
93
+ puts " Entry: $#{position[:entry_price]}"
94
+ puts " Current: $#{stock[:price]}"
95
+ puts " Stop: $#{trailing_stop.round(2)}"
96
+ puts " Profit: #{position[:profit_pct].round(1)}%"
97
+ puts " ACTION: SELL to lock profits"
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ rule "position_sizing" do
104
+ priority 8
105
+ on :signal,
106
+ action: "buy",
107
+ confidence: satisfies { |c| c > 0.6 }
108
+ on :portfolio, cash: satisfies { |c| c > 1000 }
109
+
110
+ perform do |facts|
111
+ signal = facts.find { |f| f.type == :signal }
112
+ portfolio = facts.find { |f| f.type == :portfolio }
113
+ if signal && portfolio
114
+ kelly = (signal[:confidence] * signal[:expected_return] - (1 - signal[:confidence])) / signal[:expected_return]
115
+ position_size = portfolio[:cash] * [kelly * 0.25, 0.1].min
116
+ puts "📊 POSITION SIZING: #{signal[:symbol]}"
117
+ puts " Confidence: #{(signal[:confidence] * 100).round}%"
118
+ puts " Kelly %: #{(kelly * 100).round(1)}%"
119
+ puts " Suggested Size: $#{position_size.round(0)}"
120
+ end
121
+ end
122
+ end
123
+
124
+ rule "earnings_play" do
125
+ priority 11
126
+ on :earnings,
127
+ days_until: satisfies { |d| d.between?(1, 5) },
128
+ expected_move: satisfies { |m| m > 5 }
129
+ on :options,
130
+ iv: satisfies { |v| v > 30 },
131
+ iv_rank: satisfies { |r| r > 50 }
132
+
133
+ perform do |facts|
134
+ earnings = facts.find { |f| f.type == :earnings }
135
+ options = facts.find { |f| f.type == :options }
136
+ puts "💰 EARNINGS PLAY: #{earnings[:symbol]}"
137
+ puts " Days to Earnings: #{earnings[:days_until]}"
138
+ puts " Expected Move: ±#{earnings[:expected_move]}%"
139
+ puts " IV: #{options[:iv]}% (Rank: #{options[:iv_rank]})"
140
+ puts " ACTION: Consider volatility strategy"
141
+ end
142
+ end
143
+
144
+ rule "sector_rotation" do
145
+ priority 7
146
+ on :sector,
147
+ performance: satisfies { |p| p > 1.1 },
148
+ trend: "up"
149
+ on :position,
150
+ sector: satisfies { |s| s != nil },
151
+ profit_pct: satisfies { |p| p < 2 }
152
+
153
+ perform do |facts|
154
+ strong_sector = facts.find { |f| f.type == :sector }
155
+ weak_position = facts.find { |f| f.type == :position }
156
+ if strong_sector && weak_position && strong_sector[:name] != weak_position[:sector]
157
+ puts "🔄 SECTOR ROTATION"
158
+ puts " From: #{weak_position[:sector]} (underperforming)"
159
+ puts " To: #{strong_sector[:name]} (RS: #{strong_sector[:performance]})"
160
+ puts " ACTION: Rotate portfolio allocation"
161
+ end
162
+ end
163
+ end
164
+
165
+ rule "risk_concentration" do
166
+ priority 16
167
+ on :portfolio_metrics, concentration: satisfies { |c| c > 0.3 }
168
+ on :market, volatility: satisfies { |v| v > 25 }
169
+
170
+ perform do |facts|
171
+ metrics = facts.find { |f| f.type == :portfolio_metrics }
172
+ puts "⚠️ RISK CONCENTRATION ALERT"
173
+ puts " Top Position: #{(metrics[:concentration] * 100).round}% of portfolio"
174
+ puts " Market Volatility: Elevated"
175
+ puts " ACTION: Reduce position sizes for risk management"
176
+ end
177
+ end
178
+
179
+ rule "vwap_reversion" do
180
+ priority 9
181
+ on :intraday, distance_from_vwap: satisfies { |d| d.abs > 2 }
182
+
183
+ perform do |facts|
184
+ intraday = facts.find { |f| f.type == :intraday }
185
+ direction = intraday[:distance_from_vwap] > 0 ? "OVERBOUGHT" : "OVERSOLD"
186
+ puts "📊 VWAP REVERSION: #{intraday[:symbol]}"
187
+ puts " Status: #{direction}"
188
+ puts " Distance: #{intraday[:distance_from_vwap].round(1)} std devs"
189
+ puts " Current: $#{intraday[:price]}"
190
+ puts " VWAP: $#{intraday[:vwap].round(2)}"
191
+ puts " ACTION: Mean reversion trade"
192
+ end
193
+ end
194
+
195
+ rule "news_sentiment" do
196
+ priority 13
197
+ on :news,
198
+ sentiment: satisfies { |s| s.abs > 0.7 },
199
+ volume: satisfies { |v| v > 10 }
200
+ on :stock, price_change: satisfies { |p| p.abs < 2 }
201
+
202
+ perform do |facts|
203
+ news = facts.find { |f| f.type == :news }
204
+ stock = facts.find { |f| f.type == :stock }
205
+ sentiment = news[:sentiment] > 0 ? "POSITIVE" : "NEGATIVE"
206
+ action = news[:sentiment] > 0 ? "BUY" : "SELL"
207
+ puts "📰 NEWS CATALYST: #{stock[:symbol]}"
208
+ puts " Sentiment: #{sentiment} (#{news[:sentiment].round(2)})"
209
+ puts " News Volume: #{news[:volume]} articles"
210
+ puts " Price Reaction: #{stock[:price_change].round(1)}% (lagging)"
211
+ puts " ACTION: #{action} on sentiment divergence"
212
+ end
213
+ end
214
+
215
+ rule "correlation_warning" do
216
+ priority 6
217
+ on :correlation, value: satisfies { |v| v > 0.8 }
218
+
219
+ perform do |facts|
220
+ corr = facts.find { |f| f.type == :correlation }
221
+ puts "⚠️ HIGH CORRELATION"
222
+ puts " Pairs: #{corr[:symbol1]} <-> #{corr[:symbol2]}"
223
+ puts " Correlation: #{corr[:value].round(2)}"
224
+ puts " ACTION: Diversify or hedge positions"
225
+ end
226
+ end
227
+
228
+ rule "gap_fade" do
229
+ priority 8
230
+ on :gap, size: satisfies { |s| s.abs > 2 }
231
+ on :stock, atr: satisfies { |a| a > 0 }
232
+
233
+ perform do |facts|
234
+ gap = facts.find { |f| f.type == :gap }
235
+ stock = facts.find { |f| f.type == :stock }
236
+ if gap && stock
237
+ gap_multiple = gap[:size].abs / (stock[:atr] / stock[:price] * 100)
238
+ if gap_multiple > 2
239
+ direction = gap[:size] > 0 ? "SHORT" : "LONG"
240
+ puts "📉 GAP FADE: #{stock[:symbol]}"
241
+ puts " Gap: #{gap[:size].round(1)}%"
242
+ puts " ATR Multiple: #{gap_multiple.round(1)}x"
243
+ puts " ACTION: #{direction} fade trade"
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ def simulate_market_tick(symbols)
252
+ symbols.each do |symbol|
253
+ @market_data[symbol] ||= {
254
+ price: 100 + rand(50),
255
+ ma50: 100,
256
+ ma200: 100,
257
+ volume: 1000000,
258
+ rsi: 50,
259
+ high_water: 100
260
+ }
261
+
262
+ data = @market_data[symbol]
263
+
264
+ price_change = rand(-5.0..5.0)
265
+ data[:price] = (data[:price] * (1 + price_change / 100)).round(2)
266
+ data[:ma50] = (data[:ma50] * 0.98 + data[:price] * 0.02).round(2)
267
+ data[:ma200] = (data[:ma200] * 0.995 + data[:price] * 0.005).round(2)
268
+ data[:volume] = (1000000 * (0.5 + rand * 2)).to_i
269
+ data[:rsi] = [[data[:rsi] + rand(-10..10), 0].max, 100].min
270
+ data[:high_water] = [data[:high_water], data[:price]].max
271
+
272
+ @kb.fact :stock, {
273
+ symbol: symbol,
274
+ price: data[:price],
275
+ volume: data[:volume],
276
+ rsi: data[:rsi],
277
+ price_change: price_change,
278
+ volume_ratio: data[:volume] / 1000000.0,
279
+ atr: rand(1.0..3.0)
280
+ }
281
+
282
+ @kb.fact :technical, {
283
+ symbol: symbol,
284
+ indicator: "ma_crossover",
285
+ ma50: data[:ma50],
286
+ ma200: data[:ma200],
287
+ ma50_prev: data[:ma50] - rand(-1.0..1.0),
288
+ ma200_prev: data[:ma200] - rand(-0.5..0.5)
289
+ }
290
+
291
+ @kb.fact :support, {
292
+ symbol: symbol,
293
+ level: data[:price] * 0.95
294
+ }
295
+
296
+ if rand > 0.7
297
+ @kb.fact :intraday, {
298
+ symbol: symbol,
299
+ price: data[:price],
300
+ vwap: data[:price] + rand(-2.0..2.0),
301
+ distance_from_vwap: rand(-3.0..3.0)
302
+ }
303
+ end
304
+
305
+ if rand > 0.8
306
+ @kb.fact :news, {
307
+ symbol: symbol,
308
+ sentiment: rand(-1.0..1.0),
309
+ volume: rand(5..50)
310
+ }
311
+ end
312
+
313
+ if rand > 0.85
314
+ @kb.fact :earnings, {
315
+ symbol: symbol,
316
+ days_until: rand(1..10),
317
+ expected_move: rand(3.0..15.0)
318
+ }
319
+
320
+ @kb.fact :options, {
321
+ symbol: symbol,
322
+ iv: rand(20..80),
323
+ iv_rank: rand(0..100)
324
+ }
325
+ end
326
+
327
+ if rand > 0.9
328
+ @kb.fact :gap, {
329
+ symbol: symbol,
330
+ size: rand(-5.0..5.0)
331
+ }
332
+ end
333
+ end
334
+
335
+ @kb.fact :market, {
336
+ sentiment: ["bullish", "neutral", "bearish"].sample,
337
+ volatility: rand(10..40)
338
+ }
339
+
340
+ if @portfolio[:positions].any?
341
+ total_value = @portfolio[:cash] + @portfolio[:positions].values.sum { |p| p[:value] }
342
+ max_position = @portfolio[:positions].values.map { |p| p[:value] }.max || 0
343
+ @kb.fact :portfolio_metrics, {
344
+ concentration: max_position.to_f / total_value
345
+ }
346
+ end
347
+
348
+ @kb.fact :portfolio, {
349
+ cash: @portfolio[:cash],
350
+ risk_tolerance: 0.5
351
+ }
352
+
353
+ if rand > 0.85
354
+ symbols_pair = symbols.sample(2)
355
+ @kb.fact :correlation, {
356
+ symbol1: symbols_pair[0],
357
+ symbol2: symbols_pair[1],
358
+ value: rand(0.5..0.95)
359
+ }
360
+ end
361
+
362
+ sectors = ["Technology", "Healthcare", "Finance", "Energy", "Consumer"]
363
+ @kb.fact :sector, {
364
+ name: sectors.sample,
365
+ performance: rand(0.8..1.3),
366
+ trend: ["up", "down", "sideways"].sample
367
+ }
368
+ end
369
+
370
+ def run_simulation(symbols: ["AAPL", "GOOGL", "MSFT", "NVDA"], iterations: 10)
371
+ puts "\n" + "=" * 80
372
+ puts "ADVANCED STOCK TRADING SYSTEM"
373
+ puts "=" * 80
374
+ puts "Initial Capital: $100,000"
375
+ puts "Symbols: #{symbols.join(', ')}"
376
+ puts "Rules: #{@kb.engine.rules.size} active trading strategies"
377
+ puts "=" * 80
378
+
379
+ iterations.times do |i|
380
+ puts "\n⏰ MARKET TICK #{i + 1} - #{Time.now.strftime('%H:%M:%S')}"
381
+ puts "-" * 60
382
+
383
+ @kb.reset
384
+
385
+ simulate_market_tick(symbols)
386
+
387
+ @kb.run
388
+
389
+ sleep(0.3) if i < iterations - 1
390
+ end
391
+
392
+ puts "\n" + "=" * 80
393
+ puts "SIMULATION COMPLETE"
394
+ puts "=" * 80
395
+ end
396
+ end
397
+
398
+ if __FILE__ == $0
399
+ system = AdvancedStockTradingSystem.new
400
+ system.run_simulation(
401
+ symbols: ["AAPL", "GOOGL", "MSFT", "NVDA", "TSLA", "META", "AMZN"],
402
+ iterations: 12
403
+ )
404
+ end