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,525 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/kbs/dsl'
4
+ require 'csv'
5
+ require 'date'
6
+
7
+ class CSVTradingSystem
8
+ include KBS::DSL::ConditionHelpers
9
+
10
+ def initialize(csv_file = 'sample_stock_data.csv')
11
+ @kb = nil
12
+ @csv_file = csv_file
13
+ @portfolio = {
14
+ cash: 100_000,
15
+ positions: {},
16
+ trades: [],
17
+ daily_values: []
18
+ }
19
+ @current_prices = {}
20
+ @price_history = Hash.new { |h, k| h[k] = [] }
21
+ setup_trading_rules
22
+ end
23
+
24
+ def setup_trading_rules
25
+ @kb = KBS.knowledge_base do
26
+ rule "moving_average_crossover" do
27
+ priority 15
28
+ on :technical_indicator,
29
+ indicator: "ma_crossover",
30
+ ma_20: satisfies { |ma20| ma20 && ma20 > 0 },
31
+ ma_50: satisfies { |ma50| ma50 && ma50 > 0 },
32
+ signal: "golden_cross"
33
+ on :stock_price, symbol: satisfies { |s| s && s.length > 0 }
34
+ without.on :position,
35
+ symbol: satisfies { |s| s && s.length > 0 },
36
+ status: "closed"
37
+
38
+ perform do |facts|
39
+ indicator = facts.find { |f| f.type == :technical_indicator }
40
+ price = facts.find { |f| f.type == :stock_price }
41
+
42
+ if indicator && price && indicator[:symbol] == price[:symbol]
43
+ symbol = price[:symbol]
44
+ current_price = price[:close]
45
+
46
+ puts "📈 GOLDEN CROSS: #{symbol}"
47
+ puts " 20-MA: $#{indicator[:ma_20].round(2)}"
48
+ puts " 50-MA: $#{indicator[:ma_50].round(2)}"
49
+ puts " Current Price: $#{current_price}"
50
+ puts " Signal: Strong BUY"
51
+
52
+ # Execute buy order
53
+ if @portfolio[:cash] >= current_price * 100
54
+ execute_trade(symbol, "BUY", 100, current_price, price[:date])
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ rule "rsi_oversold" do
61
+ priority 12
62
+ on :technical_indicator,
63
+ indicator: "rsi",
64
+ rsi_value: satisfies { |rsi| rsi && rsi < 30 }
65
+ on :stock_price, price_change: satisfies { |change| change && change > 1 }
66
+
67
+ perform do |facts|
68
+ indicator = facts.find { |f| f.type == :technical_indicator }
69
+ price = facts.find { |f| f.type == :stock_price }
70
+
71
+ if indicator && price && indicator[:symbol] == price[:symbol]
72
+ symbol = price[:symbol]
73
+ puts "🔄 RSI OVERSOLD BOUNCE: #{symbol}"
74
+ puts " RSI: #{indicator[:rsi_value].round(1)}"
75
+ puts " Price Change: +#{price[:price_change].round(2)}%"
76
+ puts " Signal: BUY (oversold reversal)"
77
+
78
+ if @portfolio[:cash] >= price[:close] * 50
79
+ execute_trade(symbol, "BUY", 50, price[:close], price[:date])
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ rule "take_profit" do
86
+ priority 18
87
+ on :position,
88
+ status: "open",
89
+ profit_percent: satisfies { |profit| profit && profit > 10 }
90
+
91
+ perform do |facts|
92
+ position = facts.find { |f| f.type == :position }
93
+ symbol = position[:symbol]
94
+ current_price = @current_prices[symbol]
95
+
96
+ if current_price
97
+ puts "💰 TAKE PROFIT: #{symbol}"
98
+ puts " Entry: $#{position[:entry_price]}"
99
+ puts " Current: $#{current_price}"
100
+ puts " Profit: #{position[:profit_percent].round(1)}%"
101
+ puts " Signal: SELL (take profit)"
102
+
103
+ execute_trade(symbol, "SELL", position[:shares], current_price, position[:current_date])
104
+ end
105
+ end
106
+ end
107
+
108
+ rule "stop_loss" do
109
+ priority 20
110
+ on :position,
111
+ status: "open",
112
+ loss_percent: satisfies { |loss| loss && loss > 8 }
113
+
114
+ perform do |facts|
115
+ position = facts.find { |f| f.type == :position }
116
+ symbol = position[:symbol]
117
+ current_price = @current_prices[symbol]
118
+
119
+ if current_price
120
+ puts "🛑 STOP LOSS: #{symbol}"
121
+ puts " Entry: $#{position[:entry_price]}"
122
+ puts " Current: $#{current_price}"
123
+ puts " Loss: #{position[:loss_percent].round(1)}%"
124
+ puts " Signal: SELL (stop loss)"
125
+
126
+ execute_trade(symbol, "SELL", position[:shares], current_price, position[:current_date])
127
+ end
128
+ end
129
+ end
130
+
131
+ rule "price_breakout" do
132
+ priority 13
133
+ on :technical_indicator,
134
+ indicator: "breakout",
135
+ resistance_break: true,
136
+ volume_confirmation: true
137
+
138
+ perform do |facts|
139
+ indicator = facts.find { |f| f.type == :technical_indicator }
140
+ symbol = indicator[:symbol]
141
+
142
+ puts "🚀 BREAKOUT: #{symbol}"
143
+ puts " Resistance Level: $#{indicator[:resistance_level]}"
144
+ puts " Current Price: $#{indicator[:current_price]}"
145
+ puts " Volume Spike: #{indicator[:volume_ratio]}x"
146
+ puts " Signal: BUY (momentum breakout)"
147
+
148
+ if @portfolio[:cash] >= indicator[:current_price] * 75
149
+ execute_trade(symbol, "BUY", 75, indicator[:current_price], indicator[:date])
150
+ end
151
+ end
152
+ end
153
+
154
+ rule "trend_following" do
155
+ priority 10
156
+ on :technical_indicator,
157
+ indicator: "trend",
158
+ trend_direction: "up",
159
+ trend_strength: satisfies { |strength| strength && strength > 0.7 }
160
+ on :stock_price, volume: satisfies { |vol| vol && vol > 50_000_000 }
161
+
162
+ perform do |facts|
163
+ indicator = facts.find { |f| f.type == :technical_indicator }
164
+ price = facts.find { |f| f.type == :stock_price }
165
+
166
+ if indicator && price && indicator[:symbol] == price[:symbol]
167
+ symbol = price[:symbol]
168
+ puts "📊 TREND FOLLOWING: #{symbol}"
169
+ puts " Trend Strength: #{(indicator[:trend_strength] * 100).round(1)}%"
170
+ puts " Volume: #{(price[:volume] / 1_000_000.0).round(1)}M"
171
+ puts " Signal: BUY (strong uptrend)"
172
+
173
+ if @portfolio[:cash] >= price[:close] * 60
174
+ execute_trade(symbol, "BUY", 60, price[:close], price[:date])
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ def calculate_moving_average(prices, period)
183
+ return nil if prices.length < period
184
+ prices.last(period).sum / period.to_f
185
+ end
186
+
187
+ def calculate_rsi(prices, period = 14)
188
+ return 50 if prices.length < period + 1
189
+
190
+ gains = []
191
+ losses = []
192
+
193
+ (1...prices.length).each do |i|
194
+ change = prices[i] - prices[i-1]
195
+ if change > 0
196
+ gains << change
197
+ losses << 0
198
+ else
199
+ gains << 0
200
+ losses << change.abs
201
+ end
202
+ end
203
+
204
+ return 50 if gains.length < period
205
+
206
+ avg_gain = gains.last(period).sum / period.to_f
207
+ avg_loss = losses.last(period).sum / period.to_f
208
+
209
+ return 100 if avg_loss == 0
210
+
211
+ rs = avg_gain / avg_loss
212
+ 100 - (100 / (1 + rs))
213
+ end
214
+
215
+ def detect_breakout(symbol, current_price, prices, volume, avg_volume)
216
+ return false if prices.length < 20
217
+
218
+ # Calculate resistance level (highest high of last 20 days)
219
+ resistance = prices.last(20).max
220
+
221
+ # Check if price breaks above resistance with volume confirmation
222
+ price_break = current_price > resistance * 1.01 # 1% above resistance
223
+ volume_confirmation = volume > avg_volume * 1.5 # 50% above average volume
224
+
225
+ {
226
+ resistance_break: price_break,
227
+ volume_confirmation: volume_confirmation,
228
+ resistance_level: resistance,
229
+ current_price: current_price,
230
+ volume_ratio: volume / avg_volume.to_f
231
+ }
232
+ end
233
+
234
+ def calculate_trend_strength(prices)
235
+ return 0.5 if prices.length < 10
236
+
237
+ # Simple trend strength based on price momentum
238
+ recent_avg = prices.last(5).sum / 5.0
239
+ older_avg = prices[-10..-6].sum / 5.0
240
+
241
+ strength = (recent_avg - older_avg) / older_avg
242
+ [[strength, 0].max, 1].min # Clamp between 0 and 1
243
+ end
244
+
245
+ def execute_trade(symbol, action, shares, price, date)
246
+ if action == "BUY"
247
+ cost = shares * price
248
+ if @portfolio[:cash] >= cost
249
+ @portfolio[:cash] -= cost
250
+ @portfolio[:positions][symbol] = {
251
+ symbol: symbol,
252
+ shares: shares,
253
+ entry_price: price,
254
+ entry_date: date,
255
+ status: "open"
256
+ }
257
+
258
+ trade = {
259
+ symbol: symbol,
260
+ action: action,
261
+ shares: shares,
262
+ price: price,
263
+ date: date,
264
+ value: cost
265
+ }
266
+ @portfolio[:trades] << trade
267
+
268
+ puts " ✅ EXECUTED: #{action} #{shares} shares of #{symbol} at $#{price}"
269
+ puts " 💰 Cash Remaining: $#{@portfolio[:cash].round(2)}"
270
+ end
271
+ elsif action == "SELL"
272
+ if @portfolio[:positions][symbol] && @portfolio[:positions][symbol][:status] == "open"
273
+ proceeds = shares * price
274
+ @portfolio[:cash] += proceeds
275
+
276
+ position = @portfolio[:positions][symbol]
277
+ profit = proceeds - (position[:shares] * position[:entry_price])
278
+
279
+ @portfolio[:positions][symbol][:status] = "closed"
280
+ @portfolio[:positions][symbol][:exit_price] = price
281
+ @portfolio[:positions][symbol][:exit_date] = date
282
+ @portfolio[:positions][symbol][:profit] = profit
283
+
284
+ trade = {
285
+ symbol: symbol,
286
+ action: action,
287
+ shares: shares,
288
+ price: price,
289
+ date: date,
290
+ value: proceeds,
291
+ profit: profit
292
+ }
293
+ @portfolio[:trades] << trade
294
+
295
+ puts " ✅ EXECUTED: #{action} #{shares} shares of #{symbol} at $#{price}"
296
+ puts " 💰 Profit/Loss: $#{profit.round(2)}"
297
+ puts " 💰 Cash: $#{@portfolio[:cash].round(2)}"
298
+ end
299
+ end
300
+ end
301
+
302
+ def update_positions(date)
303
+ @portfolio[:positions].each do |symbol, position|
304
+ if position[:status] == "open" && @current_prices[symbol]
305
+ current_price = @current_prices[symbol]
306
+ entry_price = position[:entry_price]
307
+ shares = position[:shares]
308
+
309
+ current_value = shares * current_price
310
+ entry_value = shares * entry_price
311
+
312
+ profit_loss = current_value - entry_value
313
+ profit_percent = (profit_loss / entry_value) * 100
314
+ loss_percent = profit_percent < 0 ? profit_percent.abs : 0
315
+
316
+ position[:current_price] = current_price
317
+ position[:current_value] = current_value
318
+ position[:unrealized_pnl] = profit_loss
319
+ position[:profit_percent] = profit_percent > 0 ? profit_percent : 0
320
+ position[:loss_percent] = loss_percent
321
+ position[:current_date] = date
322
+
323
+ # Add updated position as fact for rules to evaluate
324
+ @kb.fact :position, position
325
+ end
326
+ end
327
+ end
328
+
329
+ def process_csv_data
330
+ puts "🏦 CSV TRADING SYSTEM - Historical Backtesting"
331
+ puts "=" * 70
332
+ puts "Initial Capital: $#{@portfolio[:cash].to_s.reverse.scan(/\d{1,3}/).join(',').reverse}"
333
+ puts "=" * 70
334
+
335
+ CSV.foreach(@csv_file, headers: true) do |row|
336
+ date = Date.parse(row['Date'])
337
+ symbol = row['Symbol']
338
+ open_price = row['Open'].to_f
339
+ high = row['High'].to_f
340
+ low = row['Low'].to_f
341
+ close = row['Close'].to_f
342
+ volume = row['Volume'].to_i
343
+
344
+ # Store price history
345
+ @price_history[symbol] << close
346
+ @current_prices[symbol] = close
347
+
348
+ # Calculate price change from previous day
349
+ price_change = 0
350
+ if @price_history[symbol].length > 1
351
+ prev_close = @price_history[symbol][-2]
352
+ price_change = ((close - prev_close) / prev_close) * 100
353
+ end
354
+
355
+ puts "\n📅 #{date} - Processing #{symbol}"
356
+ puts " OHLC: $#{open_price} / $#{high} / $#{low} / $#{close}"
357
+ puts " Volume: #{(volume / 1_000_000.0).round(1)}M"
358
+ puts " Change: #{price_change >= 0 ? '+' : ''}#{price_change.round(2)}%"
359
+
360
+ # Clear previous facts
361
+ @kb.reset
362
+
363
+ # Add current price fact
364
+ @kb.fact :stock_price, {
365
+ symbol: symbol,
366
+ date: date,
367
+ open: open_price,
368
+ high: high,
369
+ low: low,
370
+ close: close,
371
+ volume: volume,
372
+ price_change: price_change
373
+ }
374
+
375
+ # Calculate and add technical indicators
376
+ if @price_history[symbol].length >= 50
377
+ ma_20 = calculate_moving_average(@price_history[symbol], 20)
378
+ ma_50 = calculate_moving_average(@price_history[symbol], 50)
379
+
380
+ # Golden Cross detection
381
+ if ma_20 && ma_50 && ma_20 > ma_50
382
+ prev_ma_20 = calculate_moving_average(@price_history[symbol][0..-2], 20)
383
+ prev_ma_50 = calculate_moving_average(@price_history[symbol][0..-2], 50)
384
+
385
+ if prev_ma_20 && prev_ma_50 && prev_ma_20 <= prev_ma_50
386
+ @kb.fact :technical_indicator, {
387
+ symbol: symbol,
388
+ indicator: "ma_crossover",
389
+ ma_20: ma_20,
390
+ ma_50: ma_50,
391
+ signal: "golden_cross",
392
+ date: date
393
+ }
394
+ end
395
+ end
396
+ end
397
+
398
+ # RSI calculation
399
+ if @price_history[symbol].length >= 15
400
+ rsi = calculate_rsi(@price_history[symbol])
401
+ @kb.fact :technical_indicator, {
402
+ symbol: symbol,
403
+ indicator: "rsi",
404
+ rsi_value: rsi,
405
+ date: date
406
+ }
407
+ end
408
+
409
+ # Breakout detection
410
+ if @price_history[symbol].length >= 20
411
+ avg_volume = @price_history[symbol].length >= 20 ?
412
+ @price_history[symbol].last(20).sum / 20.0 * 45_000_000 : 45_000_000
413
+
414
+ breakout_data = detect_breakout(symbol, close, @price_history[symbol], volume, avg_volume)
415
+
416
+ if breakout_data[:resistance_break] && breakout_data[:volume_confirmation]
417
+ @kb.fact :technical_indicator, {
418
+ symbol: symbol,
419
+ indicator: "breakout",
420
+ resistance_break: true,
421
+ volume_confirmation: true,
422
+ resistance_level: breakout_data[:resistance_level],
423
+ current_price: close,
424
+ volume_ratio: breakout_data[:volume_ratio],
425
+ date: date
426
+ }
427
+ end
428
+ end
429
+
430
+ # Trend analysis
431
+ if @price_history[symbol].length >= 10
432
+ trend_strength = calculate_trend_strength(@price_history[symbol])
433
+
434
+ @kb.fact :technical_indicator, {
435
+ symbol: symbol,
436
+ indicator: "trend",
437
+ trend_direction: trend_strength > 0.6 ? "up" : "down",
438
+ trend_strength: trend_strength,
439
+ date: date
440
+ }
441
+ end
442
+
443
+ # Update existing positions
444
+ update_positions(date)
445
+
446
+ # Run the inference engine
447
+ @kb.run
448
+
449
+ # Calculate portfolio value
450
+ portfolio_value = @portfolio[:cash]
451
+ @portfolio[:positions].each do |sym, pos|
452
+ if pos[:status] == "open"
453
+ portfolio_value += pos[:shares] * @current_prices[sym]
454
+ end
455
+ end
456
+
457
+ @portfolio[:daily_values] << {
458
+ date: date,
459
+ portfolio_value: portfolio_value,
460
+ cash: @portfolio[:cash],
461
+ positions_value: portfolio_value - @portfolio[:cash]
462
+ }
463
+
464
+ puts " 📊 Portfolio Value: $#{portfolio_value.round(2)}"
465
+ end
466
+
467
+ print_final_results
468
+ end
469
+
470
+ def print_final_results
471
+ puts "\n" + "=" * 70
472
+ puts "FINAL TRADING RESULTS"
473
+ puts "=" * 70
474
+
475
+ # Calculate final portfolio value
476
+ final_value = @portfolio[:cash]
477
+ @portfolio[:positions].each do |symbol, position|
478
+ if position[:status] == "open"
479
+ final_value += position[:shares] * @current_prices[symbol]
480
+ end
481
+ end
482
+
483
+ initial_value = 100_000
484
+ total_return = final_value - initial_value
485
+ return_pct = (total_return / initial_value) * 100
486
+
487
+ puts "Initial Capital: $#{initial_value.to_s.reverse.scan(/\d{1,3}/).join(',').reverse}"
488
+ puts "Final Value: $#{final_value.round(2).to_s.reverse.scan(/\d{1,3}/).join(',').reverse}"
489
+ puts "Total Return: $#{total_return.round(2)} (#{return_pct.round(2)}%)"
490
+ puts "Cash: $#{@portfolio[:cash].round(2)}"
491
+
492
+ puts "\nOpen Positions:"
493
+ @portfolio[:positions].each do |symbol, position|
494
+ if position[:status] == "open"
495
+ current_value = position[:shares] * @current_prices[symbol]
496
+ puts " #{symbol}: #{position[:shares]} shares @ $#{@current_prices[symbol]} = $#{current_value.round(2)}"
497
+ end
498
+ end
499
+
500
+ puts "\nTrade Summary:"
501
+ puts "Total Trades: #{@portfolio[:trades].length}"
502
+
503
+ buy_trades = @portfolio[:trades].select { |t| t[:action] == "BUY" }
504
+ sell_trades = @portfolio[:trades].select { |t| t[:action] == "SELL" }
505
+
506
+ puts "Buy Orders: #{buy_trades.length}"
507
+ puts "Sell Orders: #{sell_trades.length}"
508
+
509
+ if sell_trades.any?
510
+ profitable_trades = sell_trades.select { |t| t[:profit] > 0 }
511
+ win_rate = (profitable_trades.length.to_f / sell_trades.length) * 100
512
+ puts "Win Rate: #{win_rate.round(1)}%"
513
+
514
+ total_profit = sell_trades.sum { |t| t[:profit] }
515
+ puts "Realized P&L: $#{total_profit.round(2)}"
516
+ end
517
+
518
+ puts "\n" + "=" * 70
519
+ end
520
+ end
521
+
522
+ if __FILE__ == $0
523
+ system = CSVTradingSystem.new('sample_stock_data.csv')
524
+ system.process_csv_data
525
+ end
Binary file
@@ -6,7 +6,7 @@ require 'date'
6
6
 
7
7
  class PortfolioRebalancingSystem
8
8
  def initialize(csv_file = 'sample_stock_data.csv')
9
- @engine = KBS::ReteEngine.new
9
+ @engine = KBS::Engine.new
10
10
  @csv_file = csv_file
11
11
  @portfolio = {
12
12
  cash: 100_000,
@@ -444,7 +444,7 @@ class PortfolioRebalancingSystem
444
444
  return if sector_positions.empty? || amount <= 0
445
445
 
446
446
  # Sell proportionally from sector positions
447
- total_sector_value = sector_positions.sum do |symbol, position|
447
+ total_sector_value = sector_positions.map do |symbol, position|
448
448
  position[:shares] * @current_prices[symbol] if @current_prices[symbol]
449
449
  end.compact.sum
450
450