kbs 0.0.1 → 0.1.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 (86) 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 +235 -334
  5. data/docs/DOCUMENTATION_STATUS.md +158 -0
  6. data/docs/advanced/custom-persistence.md +775 -0
  7. data/docs/advanced/debugging.md +726 -0
  8. data/docs/advanced/index.md +8 -0
  9. data/docs/advanced/performance.md +832 -0
  10. data/docs/advanced/testing.md +691 -0
  11. data/docs/api/blackboard.md +1157 -0
  12. data/docs/api/engine.md +978 -0
  13. data/docs/api/facts.md +1212 -0
  14. data/docs/api/index.md +12 -0
  15. data/docs/api/rules.md +1034 -0
  16. data/docs/architecture/blackboard.md +553 -0
  17. data/docs/architecture/index.md +277 -0
  18. data/docs/architecture/network-structure.md +343 -0
  19. data/docs/architecture/rete-algorithm.md +737 -0
  20. data/docs/assets/css/custom.css +83 -0
  21. data/docs/assets/images/blackboard-architecture.svg +136 -0
  22. data/docs/assets/images/compiled-network.svg +101 -0
  23. data/docs/assets/images/fact-assertion-flow.svg +117 -0
  24. data/docs/assets/images/kbs.jpg +0 -0
  25. data/docs/assets/images/pattern-matching-trace.svg +136 -0
  26. data/docs/assets/images/rete-network-layers.svg +96 -0
  27. data/docs/assets/images/system-layers.svg +69 -0
  28. data/docs/assets/images/trading-signal-network.svg +139 -0
  29. data/docs/assets/js/mathjax.js +17 -0
  30. data/docs/examples/expert-systems.md +1031 -0
  31. data/docs/examples/index.md +9 -0
  32. data/docs/examples/multi-agent.md +1335 -0
  33. data/docs/examples/stock-trading.md +488 -0
  34. data/docs/guides/blackboard-memory.md +558 -0
  35. data/docs/guides/dsl.md +1321 -0
  36. data/docs/guides/facts.md +652 -0
  37. data/docs/guides/getting-started.md +383 -0
  38. data/docs/guides/index.md +23 -0
  39. data/docs/guides/negation.md +529 -0
  40. data/docs/guides/pattern-matching.md +561 -0
  41. data/docs/guides/persistence.md +451 -0
  42. data/docs/guides/variable-binding.md +491 -0
  43. data/docs/guides/writing-rules.md +755 -0
  44. data/docs/index.md +157 -0
  45. data/docs/installation.md +156 -0
  46. data/docs/quick-start.md +228 -0
  47. data/examples/README.md +2 -2
  48. data/examples/advanced_example.rb +2 -2
  49. data/examples/advanced_example_dsl.rb +224 -0
  50. data/examples/ai_enhanced_kbs.rb +1 -1
  51. data/examples/ai_enhanced_kbs_dsl.rb +538 -0
  52. data/examples/blackboard_demo_dsl.rb +50 -0
  53. data/examples/car_diagnostic.rb +1 -1
  54. data/examples/car_diagnostic_dsl.rb +54 -0
  55. data/examples/concurrent_inference_demo.rb +5 -5
  56. data/examples/concurrent_inference_demo_dsl.rb +363 -0
  57. data/examples/csv_trading_system.rb +1 -1
  58. data/examples/csv_trading_system_dsl.rb +525 -0
  59. data/examples/knowledge_base.db +0 -0
  60. data/examples/portfolio_rebalancing_system.rb +2 -2
  61. data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
  62. data/examples/redis_trading_demo_dsl.rb +177 -0
  63. data/examples/run_all.rb +50 -0
  64. data/examples/run_all_dsl.rb +49 -0
  65. data/examples/stock_trading_advanced.rb +1 -1
  66. data/examples/stock_trading_advanced_dsl.rb +404 -0
  67. data/examples/temp.txt +7693 -0
  68. data/examples/temp_dsl.txt +8447 -0
  69. data/examples/timestamped_trading.rb +1 -1
  70. data/examples/timestamped_trading_dsl.rb +258 -0
  71. data/examples/trading_demo.rb +1 -1
  72. data/examples/trading_demo_dsl.rb +322 -0
  73. data/examples/working_demo.rb +1 -1
  74. data/examples/working_demo_dsl.rb +160 -0
  75. data/lib/kbs/blackboard/engine.rb +3 -3
  76. data/lib/kbs/blackboard/fact.rb +1 -1
  77. data/lib/kbs/condition.rb +1 -1
  78. data/lib/kbs/dsl/knowledge_base.rb +1 -1
  79. data/lib/kbs/dsl/variable.rb +1 -1
  80. data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
  81. data/lib/kbs/fact.rb +1 -1
  82. data/lib/kbs/version.rb +1 -1
  83. data/lib/kbs.rb +2 -2
  84. data/mkdocs.yml +181 -0
  85. metadata +66 -6
  86. data/examples/stock_trading_system.rb.bak +0 -563
@@ -0,0 +1,488 @@
1
+ # Stock Trading System
2
+
3
+ Complete algorithmic trading system using KBS with market data collection, signal generation, risk management, and order execution.
4
+
5
+ ## System Overview
6
+
7
+ This example demonstrates a production-ready trading system with:
8
+
9
+ - **Market Data Agent** - Fetches real-time quotes
10
+ - **Signal Agent** - Generates buy/sell signals using technical indicators
11
+ - **Risk Agent** - Validates trades against risk limits
12
+ - **Execution Agent** - Submits orders to broker
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ Market Data Agent → Blackboard → Signal Agent → Risk Agent → Execution Agent
18
+
19
+ Persistent Storage (SQLite/Redis)
20
+
21
+ Audit Trail
22
+ ```
23
+
24
+ ## Complete Implementation
25
+
26
+ ```ruby
27
+ require 'kbs'
28
+ require 'net/http'
29
+ require 'json'
30
+
31
+ class TradingSystem
32
+ def initialize(db_path: 'trading.db')
33
+ @engine = KBS::Blackboard::Engine.new(db_path: db_path)
34
+ setup_rules
35
+ end
36
+
37
+ def setup_rules
38
+ # Rule 1: Generate moving average crossover signals
39
+ signal_rule = KBS::Rule.new("ma_crossover_signal", priority: 100) do |r|
40
+ r.conditions = [
41
+ KBS::Condition.new(:market_data, {
42
+ symbol: :sym?,
43
+ price: :price?,
44
+ ma_short: :ma_short?,
45
+ ma_long: :ma_long?
46
+ }),
47
+
48
+ # No existing signal for this symbol
49
+ KBS::Condition.new(:signal, { symbol: :sym? }, negated: true)
50
+ ]
51
+
52
+ r.action = lambda do |facts, bindings|
53
+ short = bindings[:ma_short?]
54
+ long = bindings[:ma_long?]
55
+
56
+ # Golden cross: short MA crosses above long MA
57
+ if short > long && (short - long) / long > 0.01 # 1% threshold
58
+ @engine.add_fact(:signal, {
59
+ symbol: bindings[:sym?],
60
+ type: "buy",
61
+ price: bindings[:price?],
62
+ confidence: calculate_confidence(short, long),
63
+ timestamp: Time.now
64
+ })
65
+ # Death cross: short MA crosses below long MA
66
+ elsif short < long && (long - short) / long > 0.01
67
+ @engine.add_fact(:signal, {
68
+ symbol: bindings[:sym?],
69
+ type: "sell",
70
+ price: bindings[:price?],
71
+ confidence: calculate_confidence(short, long),
72
+ timestamp: Time.now
73
+ })
74
+ end
75
+ end
76
+ end
77
+
78
+ # Rule 2: Risk check for buy signals
79
+ risk_check_buy = KBS::Rule.new("risk_check_buy", priority: 90) do |r|
80
+ r.conditions = [
81
+ KBS::Condition.new(:signal, {
82
+ symbol: :sym?,
83
+ type: "buy",
84
+ price: :price?
85
+ }),
86
+
87
+ KBS::Condition.new(:portfolio, {
88
+ cash: :cash?,
89
+ positions: :positions?
90
+ }),
91
+
92
+ # No risk approval yet
93
+ KBS::Condition.new(:risk_approved, { signal_id: :sig_id? }, negated: true)
94
+ ]
95
+
96
+ r.action = lambda do |facts, bindings|
97
+ signal = facts.find { |f| f.type == :signal }
98
+ cash = bindings[:cash?]
99
+ positions = bindings[:positions?]
100
+ price = bindings[:price?]
101
+
102
+ # Risk checks
103
+ position_size = calculate_position_size(cash, price)
104
+ max_position_value = cash * 0.10 # Max 10% of cash per position
105
+
106
+ if position_size * price <= max_position_value
107
+ # Check portfolio concentration
108
+ total_positions = positions.size
109
+
110
+ if total_positions < 10 # Max 10 positions
111
+ @engine.add_fact(:risk_approved, {
112
+ signal_id: signal.id,
113
+ symbol: bindings[:sym?],
114
+ quantity: position_size,
115
+ approved_at: Time.now
116
+ })
117
+ else
118
+ @engine.add_fact(:risk_rejected, {
119
+ signal_id: signal.id,
120
+ reason: "Portfolio concentration limit"
121
+ })
122
+ end
123
+ else
124
+ @engine.add_fact(:risk_rejected, {
125
+ signal_id: signal.id,
126
+ reason: "Position size exceeds limits"
127
+ })
128
+ end
129
+ end
130
+ end
131
+
132
+ # Rule 3: Execute approved orders
133
+ execution_rule = KBS::Rule.new("execute_approved_orders", priority: 80) do |r|
134
+ r.conditions = [
135
+ KBS::Condition.new(:risk_approved, {
136
+ signal_id: :sig_id?,
137
+ symbol: :sym?,
138
+ quantity: :qty?
139
+ }),
140
+
141
+ KBS::Condition.new(:signal, {
142
+ symbol: :sym?,
143
+ type: :type?,
144
+ price: :price?
145
+ }),
146
+
147
+ # Not yet executed
148
+ KBS::Condition.new(:order, { signal_id: :sig_id? }, negated: true)
149
+ ]
150
+
151
+ r.action = lambda do |facts, bindings|
152
+ order_id = execute_order(
153
+ symbol: bindings[:sym?],
154
+ type: bindings[:type?],
155
+ quantity: bindings[:qty?],
156
+ price: bindings[:price?]
157
+ )
158
+
159
+ @engine.add_fact(:order, {
160
+ signal_id: bindings[:sig_id?],
161
+ order_id: order_id,
162
+ symbol: bindings[:sym?],
163
+ type: bindings[:type?],
164
+ quantity: bindings[:qty?],
165
+ price: bindings[:price?],
166
+ status: "submitted",
167
+ timestamp: Time.now
168
+ })
169
+
170
+ # Clean up signal and approval
171
+ signal = facts.find { |f| f.type == :signal }
172
+ approval = facts.find { |f| f.type == :risk_approved }
173
+ @engine.remove_fact(signal)
174
+ @engine.remove_fact(approval)
175
+ end
176
+ end
177
+
178
+ # Rule 4: Stop loss monitoring
179
+ stop_loss_rule = KBS::Rule.new("stop_loss_trigger", priority: 95) do |r|
180
+ r.conditions = [
181
+ KBS::Condition.new(:position, {
182
+ symbol: :sym?,
183
+ entry_price: :entry?,
184
+ quantity: :qty?
185
+ }),
186
+
187
+ KBS::Condition.new(:market_data, {
188
+ symbol: :sym?,
189
+ price: :current_price?
190
+ }),
191
+
192
+ KBS::Condition.new(:stop_loss_triggered, { symbol: :sym? }, negated: true)
193
+ ]
194
+
195
+ r.action = lambda do |facts, bindings|
196
+ entry = bindings[:entry?]
197
+ current = bindings[:current_price?]
198
+ loss_pct = (entry - current) / entry
199
+
200
+ # 5% stop loss
201
+ if loss_pct > 0.05
202
+ @engine.add_fact(:signal, {
203
+ symbol: bindings[:sym?],
204
+ type: "sell",
205
+ price: current,
206
+ confidence: 1.0,
207
+ reason: "stop_loss",
208
+ timestamp: Time.now
209
+ })
210
+
211
+ @engine.add_fact(:stop_loss_triggered, {
212
+ symbol: bindings[:sym?],
213
+ entry_price: entry,
214
+ exit_price: current,
215
+ loss_pct: loss_pct
216
+ })
217
+ end
218
+ end
219
+ end
220
+
221
+ # Rule 5: Take profit monitoring
222
+ take_profit_rule = KBS::Rule.new("take_profit_trigger", priority: 95) do |r|
223
+ r.conditions = [
224
+ KBS::Condition.new(:position, {
225
+ symbol: :sym?,
226
+ entry_price: :entry?,
227
+ quantity: :qty?
228
+ }),
229
+
230
+ KBS::Condition.new(:market_data, {
231
+ symbol: :sym?,
232
+ price: :current_price?
233
+ }),
234
+
235
+ KBS::Condition.new(:take_profit_triggered, { symbol: :sym? }, negated: true)
236
+ ]
237
+
238
+ r.action = lambda do |facts, bindings|
239
+ entry = bindings[:entry?]
240
+ current = bindings[:current_price?]
241
+ gain_pct = (current - entry) / entry
242
+
243
+ # 15% take profit
244
+ if gain_pct > 0.15
245
+ @engine.add_fact(:signal, {
246
+ symbol: bindings[:sym?],
247
+ type: "sell",
248
+ price: current,
249
+ confidence: 1.0,
250
+ reason: "take_profit",
251
+ timestamp: Time.now
252
+ })
253
+
254
+ @engine.add_fact(:take_profit_triggered, {
255
+ symbol: bindings[:sym?],
256
+ entry_price: entry,
257
+ exit_price: current,
258
+ gain_pct: gain_pct
259
+ })
260
+ end
261
+ end
262
+ end
263
+
264
+ @engine.add_rule(signal_rule)
265
+ @engine.add_rule(risk_check_buy)
266
+ @engine.add_rule(execution_rule)
267
+ @engine.add_rule(stop_loss_rule)
268
+ @engine.add_rule(take_profit_rule)
269
+ end
270
+
271
+ def update_market_data(symbol, price)
272
+ # Calculate moving averages
273
+ history = get_price_history(symbol, days: 50)
274
+ ma_short = calculate_ma(history, period: 10)
275
+ ma_long = calculate_ma(history, period: 50)
276
+
277
+ # Remove old market data
278
+ old = @engine.facts.find { |f| f.type == :market_data && f[:symbol] == symbol }
279
+ @engine.remove_fact(old) if old
280
+
281
+ # Add new market data
282
+ @engine.add_fact(:market_data, {
283
+ symbol: symbol,
284
+ price: price,
285
+ ma_short: ma_short,
286
+ ma_long: ma_long,
287
+ timestamp: Time.now
288
+ })
289
+ end
290
+
291
+ def update_portfolio(cash:, positions:)
292
+ old = @engine.facts.find { |f| f.type == :portfolio }
293
+ @engine.remove_fact(old) if old
294
+
295
+ @engine.add_fact(:portfolio, {
296
+ cash: cash,
297
+ positions: positions,
298
+ updated_at: Time.now
299
+ })
300
+ end
301
+
302
+ def run_cycle
303
+ @engine.run
304
+ end
305
+
306
+ private
307
+
308
+ def calculate_confidence(short_ma, long_ma)
309
+ # Confidence based on divergence
310
+ divergence = ((short_ma - long_ma).abs / long_ma)
311
+ [divergence * 10, 1.0].min
312
+ end
313
+
314
+ def calculate_position_size(cash, price)
315
+ # Kelly criterion or fixed percentage
316
+ (cash * 0.05 / price).floor # 5% of cash
317
+ end
318
+
319
+ def execute_order(symbol:, type:, quantity:, price:)
320
+ # Submit to broker API
321
+ # Returns order_id
322
+ "ORD-#{Time.now.to_i}-#{symbol}"
323
+ end
324
+
325
+ def get_price_history(symbol, days:)
326
+ # Fetch historical prices
327
+ # Returns array of prices
328
+ []
329
+ end
330
+
331
+ def calculate_ma(prices, period:)
332
+ return 0 if prices.size < period
333
+ prices.last(period).sum / period.to_f
334
+ end
335
+ end
336
+
337
+ # Usage
338
+ trading = TradingSystem.new
339
+
340
+ # Initialize portfolio
341
+ trading.update_portfolio(
342
+ cash: 100000,
343
+ positions: []
344
+ )
345
+
346
+ # Market data loop
347
+ symbols = ["AAPL", "GOOGL", "MSFT", "TSLA"]
348
+
349
+ loop do
350
+ symbols.each do |symbol|
351
+ # Fetch current price (from API)
352
+ price = fetch_price(symbol)
353
+
354
+ # Update market data
355
+ trading.update_market_data(symbol, price)
356
+ end
357
+
358
+ # Run inference engine
359
+ trading.run_cycle
360
+
361
+ sleep 60 # Run every minute
362
+ end
363
+ ```
364
+
365
+ ## Key Features
366
+
367
+ ### 1. Moving Average Crossover
368
+
369
+ Generates buy signals when short MA crosses above long MA (golden cross) and sell signals when it crosses below (death cross).
370
+
371
+ ### 2. Risk Management
372
+
373
+ - **Position sizing**: Max 10% of cash per position
374
+ - **Portfolio concentration**: Max 10 positions
375
+ - **Stop loss**: Automatic exit at 5% loss
376
+ - **Take profit**: Automatic exit at 15% gain
377
+
378
+ ### 3. Order Execution
379
+
380
+ Approved signals become orders submitted to broker.
381
+
382
+ ### 4. Audit Trail
383
+
384
+ All decisions logged to database:
385
+
386
+ ```ruby
387
+ # Query signal history
388
+ signals = trading.engine.facts.select { |f| f.type == :signal }
389
+
390
+ # Query order history
391
+ orders = trading.engine.facts.select { |f| f.type == :order }
392
+
393
+ # Audit trail
394
+ trading.engine.fact_history(signal.id)
395
+ ```
396
+
397
+ ## Performance Optimization
398
+
399
+ ### Use Redis for Real-Time Trading
400
+
401
+ ```ruby
402
+ require 'kbs/blackboard/persistence/redis_store'
403
+
404
+ store = KBS::Blackboard::Persistence::RedisStore.new(
405
+ url: 'redis://localhost:6379/0'
406
+ )
407
+
408
+ trading = TradingSystem.new(store: store)
409
+ # 100x faster updates
410
+ ```
411
+
412
+ ### Hybrid Store for Compliance
413
+
414
+ ```ruby
415
+ require 'kbs/blackboard/persistence/hybrid_store'
416
+
417
+ store = KBS::Blackboard::Persistence::HybridStore.new(
418
+ redis_url: 'redis://localhost:6379/0',
419
+ db_path: 'audit.db'
420
+ )
421
+
422
+ trading = TradingSystem.new(store: store)
423
+ # Fast + complete audit trail
424
+ ```
425
+
426
+ ## Testing
427
+
428
+ ```ruby
429
+ require 'minitest/autorun'
430
+
431
+ class TestTradingSystem < Minitest::Test
432
+ def setup
433
+ @system = TradingSystem.new(db_path: ':memory:')
434
+ end
435
+
436
+ def test_golden_cross_generates_buy_signal
437
+ @system.update_portfolio(cash: 10000, positions: [])
438
+
439
+ # Short MA above long MA
440
+ @system.update_market_data("AAPL", 150)
441
+ @system.engine.add_fact(:market_data, {
442
+ symbol: "AAPL",
443
+ price: 150,
444
+ ma_short: 155, # Higher
445
+ ma_long: 145, # Lower
446
+ timestamp: Time.now
447
+ })
448
+
449
+ @system.run_cycle
450
+
451
+ signals = @system.engine.facts.select { |f| f.type == :signal }
452
+ assert_equal 1, signals.size
453
+ assert_equal "buy", signals.first[:type]
454
+ end
455
+
456
+ def test_stop_loss_triggers_sell
457
+ @system.update_portfolio(cash: 10000, positions: [])
458
+
459
+ # Add position
460
+ @system.engine.add_fact(:position, {
461
+ symbol: "AAPL",
462
+ entry_price: 100,
463
+ quantity: 10
464
+ })
465
+
466
+ # Price drops 6%
467
+ @system.update_market_data("AAPL", 94)
468
+
469
+ @system.run_cycle
470
+
471
+ signals = @system.engine.facts.select { |f|
472
+ f.type == :signal && f[:reason] == "stop_loss"
473
+ }
474
+
475
+ assert_equal 1, signals.size
476
+ end
477
+ end
478
+ ```
479
+
480
+ ## Next Steps
481
+
482
+ - **[Multi-Agent Example](multi-agent.md)** - Distributed trading with multiple strategies
483
+ - **[Performance Guide](../advanced/performance.md)** - Optimize for high-frequency trading
484
+ - **[Blackboard Memory](../guides/blackboard-memory.md)** - Persistent state management
485
+
486
+ ---
487
+
488
+ *This trading system demonstrates production-ready algorithmic trading with KBS. Always backtest thoroughly before live trading.*