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.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +68 -2
- data/README.md +235 -334
- data/docs/DOCUMENTATION_STATUS.md +158 -0
- data/docs/advanced/custom-persistence.md +775 -0
- data/docs/advanced/debugging.md +726 -0
- data/docs/advanced/index.md +8 -0
- data/docs/advanced/performance.md +832 -0
- data/docs/advanced/testing.md +691 -0
- data/docs/api/blackboard.md +1157 -0
- data/docs/api/engine.md +978 -0
- data/docs/api/facts.md +1212 -0
- data/docs/api/index.md +12 -0
- data/docs/api/rules.md +1034 -0
- data/docs/architecture/blackboard.md +553 -0
- data/docs/architecture/index.md +277 -0
- data/docs/architecture/network-structure.md +343 -0
- data/docs/architecture/rete-algorithm.md +737 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/blackboard-architecture.svg +136 -0
- data/docs/assets/images/compiled-network.svg +101 -0
- data/docs/assets/images/fact-assertion-flow.svg +117 -0
- data/docs/assets/images/kbs.jpg +0 -0
- data/docs/assets/images/pattern-matching-trace.svg +136 -0
- data/docs/assets/images/rete-network-layers.svg +96 -0
- data/docs/assets/images/system-layers.svg +69 -0
- data/docs/assets/images/trading-signal-network.svg +139 -0
- data/docs/assets/js/mathjax.js +17 -0
- data/docs/examples/expert-systems.md +1031 -0
- data/docs/examples/index.md +9 -0
- data/docs/examples/multi-agent.md +1335 -0
- data/docs/examples/stock-trading.md +488 -0
- data/docs/guides/blackboard-memory.md +558 -0
- data/docs/guides/dsl.md +1321 -0
- data/docs/guides/facts.md +652 -0
- data/docs/guides/getting-started.md +383 -0
- data/docs/guides/index.md +23 -0
- data/docs/guides/negation.md +529 -0
- data/docs/guides/pattern-matching.md +561 -0
- data/docs/guides/persistence.md +451 -0
- data/docs/guides/variable-binding.md +491 -0
- data/docs/guides/writing-rules.md +755 -0
- data/docs/index.md +157 -0
- data/docs/installation.md +156 -0
- data/docs/quick-start.md +228 -0
- data/examples/README.md +2 -2
- data/examples/advanced_example.rb +2 -2
- data/examples/advanced_example_dsl.rb +224 -0
- data/examples/ai_enhanced_kbs.rb +1 -1
- data/examples/ai_enhanced_kbs_dsl.rb +538 -0
- data/examples/blackboard_demo_dsl.rb +50 -0
- data/examples/car_diagnostic.rb +1 -1
- data/examples/car_diagnostic_dsl.rb +54 -0
- data/examples/concurrent_inference_demo.rb +5 -5
- data/examples/concurrent_inference_demo_dsl.rb +363 -0
- data/examples/csv_trading_system.rb +1 -1
- data/examples/csv_trading_system_dsl.rb +525 -0
- data/examples/knowledge_base.db +0 -0
- data/examples/portfolio_rebalancing_system.rb +2 -2
- data/examples/portfolio_rebalancing_system_dsl.rb +613 -0
- data/examples/redis_trading_demo_dsl.rb +177 -0
- data/examples/run_all.rb +50 -0
- data/examples/run_all_dsl.rb +49 -0
- data/examples/stock_trading_advanced.rb +1 -1
- data/examples/stock_trading_advanced_dsl.rb +404 -0
- data/examples/temp.txt +7693 -0
- data/examples/temp_dsl.txt +8447 -0
- data/examples/timestamped_trading.rb +1 -1
- data/examples/timestamped_trading_dsl.rb +258 -0
- data/examples/trading_demo.rb +1 -1
- data/examples/trading_demo_dsl.rb +322 -0
- data/examples/working_demo.rb +1 -1
- data/examples/working_demo_dsl.rb +160 -0
- data/lib/kbs/blackboard/engine.rb +3 -3
- data/lib/kbs/blackboard/fact.rb +1 -1
- data/lib/kbs/condition.rb +1 -1
- data/lib/kbs/dsl/knowledge_base.rb +1 -1
- data/lib/kbs/dsl/variable.rb +1 -1
- data/lib/kbs/{rete_engine.rb → engine.rb} +1 -1
- data/lib/kbs/fact.rb +1 -1
- data/lib/kbs/version.rb +1 -1
- data/lib/kbs.rb +2 -2
- data/mkdocs.yml +181 -0
- metadata +66 -6
- data/examples/stock_trading_system.rb.bak +0 -563
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs/dsl'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
class TimestampedTradingSystem
|
|
7
|
+
include KBS::DSL::ConditionHelpers
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@kb = nil
|
|
11
|
+
@market_open = Time.parse("09:30:00")
|
|
12
|
+
@market_close = Time.parse("16:00:00")
|
|
13
|
+
setup_time_aware_rules
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def format_volume(volume)
|
|
17
|
+
if volume >= 1_000_000
|
|
18
|
+
"#{(volume / 1_000_000.0).round(1)}M"
|
|
19
|
+
elsif volume >= 1_000
|
|
20
|
+
"#{(volume / 1_000.0).round(1)}K"
|
|
21
|
+
else
|
|
22
|
+
volume.to_s
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def setup_time_aware_rules
|
|
27
|
+
ts_sys = self # Capture self for use in perform blocks
|
|
28
|
+
@kb = KBS.knowledge_base do
|
|
29
|
+
rule "fresh_momentum" do
|
|
30
|
+
priority 15
|
|
31
|
+
on :stock,
|
|
32
|
+
price_change: satisfies { |p| p && p > 2 },
|
|
33
|
+
timestamp: satisfies { |t| t && (Time.now - t) < 60 },
|
|
34
|
+
market_session: "regular"
|
|
35
|
+
|
|
36
|
+
perform do |facts|
|
|
37
|
+
stock = facts.find { |f| f.type == :stock }
|
|
38
|
+
age = Time.now - stock[:timestamp]
|
|
39
|
+
puts "🚀 FRESH MOMENTUM: #{stock[:symbol]}"
|
|
40
|
+
puts " Price Change: +#{stock[:price_change]}%"
|
|
41
|
+
puts " Data Age: #{age.round(1)} seconds"
|
|
42
|
+
puts " Market Time: #{stock[:market_time]}"
|
|
43
|
+
puts " Recommendation: BUY (fresh signal)"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
rule "stale_data_warning" do
|
|
48
|
+
priority 20
|
|
49
|
+
on :stock, timestamp: satisfies { |t| t && (Time.now - t) > 300 }
|
|
50
|
+
|
|
51
|
+
perform do |facts|
|
|
52
|
+
stock = facts.find { |f| f.type == :stock }
|
|
53
|
+
age = Time.now - stock[:timestamp]
|
|
54
|
+
puts "⚠️ STALE DATA: #{stock[:symbol]}"
|
|
55
|
+
puts " Data Age: #{(age / 60).round(1)} minutes"
|
|
56
|
+
puts " Last Update: #{stock[:timestamp]}"
|
|
57
|
+
puts " ACTION: IGNORE - Data too old"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
rule "market_hours_check" do
|
|
62
|
+
on :stock,
|
|
63
|
+
market_session: "after_hours",
|
|
64
|
+
volume: satisfies { |v| v && v > 100_000 }
|
|
65
|
+
|
|
66
|
+
perform do |facts|
|
|
67
|
+
stock = facts.find { |f| f.type == :stock }
|
|
68
|
+
puts "🌙 AFTER HOURS ACTIVITY: #{stock[:symbol]}"
|
|
69
|
+
puts " Volume: #{ts_sys.format_volume(stock[:volume])}"
|
|
70
|
+
puts " Time: #{stock[:market_time]}"
|
|
71
|
+
puts " ACTION: Monitor for gap potential"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
rule "rapid_movement" do
|
|
76
|
+
on :price_tick,
|
|
77
|
+
time_delta: satisfies { |d| d && d < 5 },
|
|
78
|
+
price_delta: satisfies { |d| d && d.abs > 0.50 }
|
|
79
|
+
|
|
80
|
+
perform do |facts|
|
|
81
|
+
tick = facts.find { |f| f.type == :price_tick }
|
|
82
|
+
direction = tick[:price_delta] > 0 ? "📈 UP" : "📉 DOWN"
|
|
83
|
+
puts "⚡ RAPID MOVEMENT: #{tick[:symbol]} #{direction}"
|
|
84
|
+
puts " Price Change: #{tick[:price_delta] > 0 ? '+' : ''}$#{tick[:price_delta]}"
|
|
85
|
+
puts " Time Frame: #{tick[:time_delta]} seconds"
|
|
86
|
+
puts " ACTION: Check for news catalyst"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
rule "opening_gap" do
|
|
91
|
+
on :stock,
|
|
92
|
+
market_time: satisfies { |t|
|
|
93
|
+
t && (t.hour == 9 && t.min >= 30 && t.min <= 35)
|
|
94
|
+
},
|
|
95
|
+
gap_percentage: satisfies { |g| g && g.abs > 1 }
|
|
96
|
+
|
|
97
|
+
perform do |facts|
|
|
98
|
+
stock = facts.find { |f| f.type == :stock }
|
|
99
|
+
direction = stock[:gap_percentage] > 0 ? "GAP UP" : "GAP DOWN"
|
|
100
|
+
puts "🔔 OPENING #{direction}: #{stock[:symbol]}"
|
|
101
|
+
puts " Gap: #{stock[:gap_percentage] > 0 ? '+' : ''}#{stock[:gap_percentage]}%"
|
|
102
|
+
puts " Time: #{stock[:market_time]}"
|
|
103
|
+
puts " Volume: #{ts_sys.format_volume(stock[:volume])}"
|
|
104
|
+
puts " ACTION: Monitor for gap fill or continuation"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
rule "end_of_day" do
|
|
109
|
+
on :stock, market_time: satisfies { |t|
|
|
110
|
+
t && (t.hour == 15 && t.min >= 45)
|
|
111
|
+
}
|
|
112
|
+
on :position, status: "open"
|
|
113
|
+
|
|
114
|
+
perform do |facts|
|
|
115
|
+
stock = facts.find { |f| f.type == :stock }
|
|
116
|
+
position = facts.find { |f| f.type == :position }
|
|
117
|
+
puts "🕐 END OF DAY: #{position[:symbol]}"
|
|
118
|
+
puts " Current Time: #{stock[:market_time]}"
|
|
119
|
+
puts " Position P&L: #{position[:unrealized_pnl]}"
|
|
120
|
+
puts " ACTION: Consider closing before market close"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def determine_market_session(time)
|
|
127
|
+
hour_min = time.hour * 100 + time.min
|
|
128
|
+
|
|
129
|
+
case hour_min
|
|
130
|
+
when 400..929 then "pre_market"
|
|
131
|
+
when 930..1559 then "regular"
|
|
132
|
+
when 1600..2000 then "after_hours"
|
|
133
|
+
else "closed"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def add_timestamped_stock_fact(symbol, price, volume, options = {})
|
|
138
|
+
timestamp = options[:timestamp] || Time.now
|
|
139
|
+
market_time = options[:market_time] || timestamp
|
|
140
|
+
|
|
141
|
+
@kb.fact :stock, {
|
|
142
|
+
symbol: symbol,
|
|
143
|
+
price: price,
|
|
144
|
+
volume: volume,
|
|
145
|
+
timestamp: timestamp,
|
|
146
|
+
market_time: market_time,
|
|
147
|
+
market_session: determine_market_session(market_time),
|
|
148
|
+
price_change: options[:price_change] || 0,
|
|
149
|
+
gap_percentage: options[:gap_percentage] || 0
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def add_price_tick(symbol, old_price, new_price, old_time, new_time)
|
|
154
|
+
price_delta = new_price - old_price
|
|
155
|
+
time_delta = new_time - old_time
|
|
156
|
+
|
|
157
|
+
@kb.fact :price_tick, {
|
|
158
|
+
symbol: symbol,
|
|
159
|
+
old_price: old_price,
|
|
160
|
+
new_price: new_price,
|
|
161
|
+
price_delta: price_delta,
|
|
162
|
+
time_delta: time_delta,
|
|
163
|
+
timestamp: new_time
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def simulate_trading_day
|
|
168
|
+
puts "🏦 TIMESTAMPED STOCK TRADING SYSTEM"
|
|
169
|
+
puts "=" * 60
|
|
170
|
+
|
|
171
|
+
base_time = Time.parse("2024-08-15 09:30:00")
|
|
172
|
+
|
|
173
|
+
# Scenario 1: Market Open with Gap
|
|
174
|
+
puts "\n📊 SCENARIO 1: Market Open Gap Up"
|
|
175
|
+
puts "-" * 40
|
|
176
|
+
@kb.reset
|
|
177
|
+
|
|
178
|
+
add_timestamped_stock_fact("AAPL", 185.50, 2_500_000, {
|
|
179
|
+
market_time: base_time + 2*60, # 9:32 AM
|
|
180
|
+
price_change: 2.1,
|
|
181
|
+
gap_percentage: 2.3
|
|
182
|
+
})
|
|
183
|
+
@kb.run
|
|
184
|
+
|
|
185
|
+
# Scenario 2: Fresh Momentum Signal
|
|
186
|
+
puts "\n📊 SCENARIO 2: Fresh Momentum (Recent Data)"
|
|
187
|
+
puts "-" * 40
|
|
188
|
+
@kb.reset
|
|
189
|
+
|
|
190
|
+
add_timestamped_stock_fact("GOOGL", 142.80, 1_200_000, {
|
|
191
|
+
timestamp: Time.now - 30, # 30 seconds ago
|
|
192
|
+
market_time: Time.now - 30,
|
|
193
|
+
price_change: 3.2
|
|
194
|
+
})
|
|
195
|
+
@kb.run
|
|
196
|
+
|
|
197
|
+
# Scenario 3: Stale Data
|
|
198
|
+
puts "\n📊 SCENARIO 3: Stale Data Warning"
|
|
199
|
+
puts "-" * 40
|
|
200
|
+
@kb.reset
|
|
201
|
+
|
|
202
|
+
add_timestamped_stock_fact("TSLA", 195.40, 800_000, {
|
|
203
|
+
timestamp: Time.now - 600, # 10 minutes ago
|
|
204
|
+
market_time: Time.now - 600,
|
|
205
|
+
price_change: 1.5
|
|
206
|
+
})
|
|
207
|
+
@kb.run
|
|
208
|
+
|
|
209
|
+
# Scenario 4: After Hours Activity
|
|
210
|
+
puts "\n📊 SCENARIO 4: After Hours Trading"
|
|
211
|
+
puts "-" * 40
|
|
212
|
+
@kb.reset
|
|
213
|
+
|
|
214
|
+
after_hours_time = Time.parse("2024-08-15 17:30:00")
|
|
215
|
+
add_timestamped_stock_fact("NVDA", 425.80, 150_000, {
|
|
216
|
+
market_time: after_hours_time,
|
|
217
|
+
timestamp: after_hours_time
|
|
218
|
+
})
|
|
219
|
+
@kb.run
|
|
220
|
+
|
|
221
|
+
# Scenario 5: Rapid Price Movement
|
|
222
|
+
puts "\n📊 SCENARIO 5: Rapid Price Tick"
|
|
223
|
+
puts "-" * 40
|
|
224
|
+
@kb.reset
|
|
225
|
+
|
|
226
|
+
tick_time = Time.now
|
|
227
|
+
add_price_tick("META", 298.50, 299.25, tick_time - 3, tick_time)
|
|
228
|
+
@kb.run
|
|
229
|
+
|
|
230
|
+
# Scenario 6: End of Day
|
|
231
|
+
puts "\n📊 SCENARIO 6: End of Trading Day"
|
|
232
|
+
puts "-" * 40
|
|
233
|
+
@kb.reset
|
|
234
|
+
|
|
235
|
+
eod_time = Time.parse("2024-08-15 15:50:00")
|
|
236
|
+
add_timestamped_stock_fact("AMZN", 142.30, 900_000, {
|
|
237
|
+
market_time: eod_time,
|
|
238
|
+
timestamp: eod_time
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
@kb.fact :position, {
|
|
242
|
+
symbol: "AMZN",
|
|
243
|
+
status: "open",
|
|
244
|
+
shares: 100,
|
|
245
|
+
entry_price: 140.00,
|
|
246
|
+
unrealized_pnl: 230.00
|
|
247
|
+
}
|
|
248
|
+
@kb.run
|
|
249
|
+
|
|
250
|
+
puts "\n" + "=" * 60
|
|
251
|
+
puts "TIMESTAMPED TRADING SIMULATION COMPLETE"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if __FILE__ == $0
|
|
256
|
+
system = TimestampedTradingSystem.new
|
|
257
|
+
system.simulate_trading_day
|
|
258
|
+
end
|
data/examples/trading_demo.rb
CHANGED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative '../lib/kbs/dsl'
|
|
4
|
+
include KBS::DSL::ConditionHelpers
|
|
5
|
+
|
|
6
|
+
class TradingDemo
|
|
7
|
+
attr_reader :kb
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@kb = create_knowledge_base
|
|
11
|
+
puts "🏦 STOCK TRADING EXPERT SYSTEM LOADED"
|
|
12
|
+
puts "📊 #{@kb.rules.size} trading strategies active"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_knowledge_base
|
|
16
|
+
demo = self # Capture self for use in perform blocks
|
|
17
|
+
KBS.knowledge_base do
|
|
18
|
+
rule "golden_cross_buy" do
|
|
19
|
+
on :ma_signal, type: "golden_cross"
|
|
20
|
+
on :stock do
|
|
21
|
+
volume greater_than(500000)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
perform do |facts, bindings|
|
|
25
|
+
stock = facts.find { |f| f.type == :stock }
|
|
26
|
+
signal = facts.find { |f| f.type == :ma_signal }
|
|
27
|
+
puts "📈 GOLDEN CROSS SIGNAL: #{stock[:symbol]}"
|
|
28
|
+
puts " 50-MA crossed above 200-MA"
|
|
29
|
+
puts " Volume: #{demo.format_volume(stock[:volume])}"
|
|
30
|
+
puts " Price: $#{stock[:price]}"
|
|
31
|
+
puts " Recommendation: STRONG BUY"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
rule "momentum_breakout" do
|
|
36
|
+
on :stock do
|
|
37
|
+
price_change greater_than(2)
|
|
38
|
+
rsi between(50, 75)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
perform do |facts, bindings|
|
|
42
|
+
stock = facts.find { |f| f.type == :stock }
|
|
43
|
+
puts "🚀 MOMENTUM BREAKOUT: #{stock[:symbol]}"
|
|
44
|
+
puts " Price Change: +#{stock[:price_change].round(1)}%"
|
|
45
|
+
puts " RSI: #{stock[:rsi].round(1)} (strong but not overbought)"
|
|
46
|
+
puts " Recommendation: BUY"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
rule "oversold_reversal" do
|
|
51
|
+
on :stock do
|
|
52
|
+
rsi less_than(35)
|
|
53
|
+
end
|
|
54
|
+
on :market, trend: "bullish"
|
|
55
|
+
|
|
56
|
+
perform do |facts, bindings|
|
|
57
|
+
stock = facts.find { |f| f.type == :stock }
|
|
58
|
+
puts "🔄 OVERSOLD REVERSAL: #{stock[:symbol]}"
|
|
59
|
+
puts " RSI: #{stock[:rsi].round(1)} (oversold)"
|
|
60
|
+
puts " Market: Bullish environment"
|
|
61
|
+
puts " Recommendation: CONTRARIAN BUY"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
rule "earnings_play" do
|
|
66
|
+
on :earnings do
|
|
67
|
+
days_until satisfies { |d| d <= 3 }
|
|
68
|
+
end
|
|
69
|
+
on :options do
|
|
70
|
+
iv greater_than(40)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
perform do |facts, bindings|
|
|
74
|
+
earnings = facts.find { |f| f.type == :earnings }
|
|
75
|
+
options = facts.find { |f| f.type == :options }
|
|
76
|
+
puts "💰 EARNINGS PLAY: #{earnings[:symbol]}"
|
|
77
|
+
puts " Earnings in #{earnings[:days_until]} day(s)"
|
|
78
|
+
puts " Implied Volatility: #{options[:iv].round(1)}%"
|
|
79
|
+
puts " Recommendation: VOLATILITY STRATEGY"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
rule "stop_loss_alert" do
|
|
84
|
+
on :position do
|
|
85
|
+
status "open"
|
|
86
|
+
loss_pct greater_than(8)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
perform do |facts, bindings|
|
|
90
|
+
position = facts.find { |f| f.type == :position }
|
|
91
|
+
puts "🛑 STOP LOSS TRIGGERED: #{position[:symbol]}"
|
|
92
|
+
puts " Loss: #{position[:loss_pct].round(1)}%"
|
|
93
|
+
puts " Entry: $#{position[:entry_price]}"
|
|
94
|
+
puts " Current: $#{position[:current_price]}"
|
|
95
|
+
puts " Recommendation: SELL IMMEDIATELY"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
rule "concentration_risk" do
|
|
100
|
+
on :portfolio do
|
|
101
|
+
concentration greater_than(25)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
perform do |facts, bindings|
|
|
105
|
+
portfolio = facts.find { |f| f.type == :portfolio }
|
|
106
|
+
puts "⚠️ CONCENTRATION RISK: #{portfolio[:top_holding]}"
|
|
107
|
+
puts " Position Size: #{portfolio[:concentration].round(1)}% of portfolio"
|
|
108
|
+
puts " Recommendation: DIVERSIFY HOLDINGS"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
rule "news_sentiment" do
|
|
113
|
+
on :news do
|
|
114
|
+
sentiment satisfies { |s| s.abs > 0.6 }
|
|
115
|
+
impact "high"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
perform do |facts, bindings|
|
|
119
|
+
news = facts.find { |f| f.type == :news }
|
|
120
|
+
sentiment = news[:sentiment] > 0 ? "POSITIVE" : "NEGATIVE"
|
|
121
|
+
action = news[:sentiment] > 0 ? "BUY" : "SELL"
|
|
122
|
+
puts "📰 NEWS CATALYST: #{news[:symbol]}"
|
|
123
|
+
puts " Sentiment: #{sentiment} (#{news[:sentiment].round(2)})"
|
|
124
|
+
puts " Impact: HIGH"
|
|
125
|
+
puts " Recommendation: #{action} ON NEWS"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
rule "sector_strength" do
|
|
130
|
+
on :sector do
|
|
131
|
+
performance greater_than(5)
|
|
132
|
+
trend "accelerating"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
perform do |facts, bindings|
|
|
136
|
+
sector = facts.find { |f| f.type == :sector }
|
|
137
|
+
puts "🔄 SECTOR ROTATION: #{sector[:name]}"
|
|
138
|
+
puts " Performance: +#{sector[:performance].round(1)}%"
|
|
139
|
+
puts " Trend: Accelerating"
|
|
140
|
+
puts " Recommendation: INCREASE ALLOCATION"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def format_volume(volume)
|
|
147
|
+
if volume >= 1_000_000
|
|
148
|
+
"#{(volume / 1_000_000.0).round(1)}M"
|
|
149
|
+
elsif volume >= 1_000
|
|
150
|
+
"#{(volume / 1_000.0).round(1)}K"
|
|
151
|
+
else
|
|
152
|
+
volume.to_s
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def generate_scenario(name, &block)
|
|
157
|
+
puts "\n" + "="*60
|
|
158
|
+
puts "SCENARIO: #{name}"
|
|
159
|
+
puts "="*60
|
|
160
|
+
|
|
161
|
+
@kb.reset
|
|
162
|
+
|
|
163
|
+
yield
|
|
164
|
+
|
|
165
|
+
puts "\nFacts in working memory:"
|
|
166
|
+
@kb.facts.each do |fact|
|
|
167
|
+
puts " #{fact}"
|
|
168
|
+
end
|
|
169
|
+
puts ""
|
|
170
|
+
|
|
171
|
+
@kb.run
|
|
172
|
+
|
|
173
|
+
puts "-"*60
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def demo_scenarios
|
|
177
|
+
generate_scenario("Bull Market with Golden Cross") do
|
|
178
|
+
@kb.fact :stock, {
|
|
179
|
+
symbol: "AAPL",
|
|
180
|
+
price: 185.50,
|
|
181
|
+
volume: 1_250_000,
|
|
182
|
+
price_change: 1.2,
|
|
183
|
+
rsi: 65
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@kb.fact :ma_signal, {
|
|
187
|
+
symbol: "AAPL",
|
|
188
|
+
type: "golden_cross"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@kb.fact :market, { trend: "bullish" }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
generate_scenario("Momentum Breakout") do
|
|
195
|
+
@kb.fact :stock, {
|
|
196
|
+
symbol: "NVDA",
|
|
197
|
+
price: 425.80,
|
|
198
|
+
volume: 980_000,
|
|
199
|
+
price_change: 4.7,
|
|
200
|
+
rsi: 68
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@kb.fact :market, { trend: "bullish" }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
generate_scenario("Oversold Bounce Opportunity") do
|
|
207
|
+
@kb.fact :stock, {
|
|
208
|
+
symbol: "TSLA",
|
|
209
|
+
price: 178.90,
|
|
210
|
+
volume: 750_000,
|
|
211
|
+
price_change: -2.1,
|
|
212
|
+
rsi: 28
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@kb.fact :market, { trend: "bullish" }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
generate_scenario("Earnings Volatility Play") do
|
|
219
|
+
@kb.fact :earnings, {
|
|
220
|
+
symbol: "GOOGL",
|
|
221
|
+
days_until: 2,
|
|
222
|
+
expected_move: 8.5
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@kb.fact :options, {
|
|
226
|
+
symbol: "GOOGL",
|
|
227
|
+
iv: 45.2,
|
|
228
|
+
iv_rank: 75
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
generate_scenario("Stop Loss Alert") do
|
|
233
|
+
@kb.fact :position, {
|
|
234
|
+
symbol: "META",
|
|
235
|
+
status: "open",
|
|
236
|
+
entry_price: 320.00,
|
|
237
|
+
current_price: 285.40,
|
|
238
|
+
loss_pct: 10.8,
|
|
239
|
+
shares: 100
|
|
240
|
+
}
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
generate_scenario("Portfolio Risk Warning") do
|
|
244
|
+
@kb.fact :portfolio, {
|
|
245
|
+
total_value: 250_000,
|
|
246
|
+
top_holding: "AAPL",
|
|
247
|
+
concentration: 32.5,
|
|
248
|
+
cash_pct: 5
|
|
249
|
+
}
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
generate_scenario("News-Driven Trade") do
|
|
253
|
+
@kb.fact :news, {
|
|
254
|
+
symbol: "MSFT",
|
|
255
|
+
sentiment: -0.75,
|
|
256
|
+
impact: "high",
|
|
257
|
+
headlines: "Major cloud outage affects services"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@kb.fact :stock, {
|
|
261
|
+
symbol: "MSFT",
|
|
262
|
+
price: 395.20,
|
|
263
|
+
price_change: -1.2,
|
|
264
|
+
volume: 890_000
|
|
265
|
+
}
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
generate_scenario("Sector Rotation Signal") do
|
|
269
|
+
@kb.fact :sector, {
|
|
270
|
+
name: "Technology",
|
|
271
|
+
performance: 7.3,
|
|
272
|
+
trend: "accelerating",
|
|
273
|
+
leaders: ["AAPL", "MSFT", "GOOGL"]
|
|
274
|
+
}
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
generate_scenario("Complex Multi-Signal Day") do
|
|
278
|
+
@kb.fact :stock, {
|
|
279
|
+
symbol: "AMZN",
|
|
280
|
+
price: 142.50,
|
|
281
|
+
volume: 1_800_000,
|
|
282
|
+
price_change: 3.8,
|
|
283
|
+
rsi: 72
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@kb.fact :ma_signal, {
|
|
287
|
+
symbol: "AMZN",
|
|
288
|
+
type: "golden_cross"
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@kb.fact :news, {
|
|
292
|
+
symbol: "AMZN",
|
|
293
|
+
sentiment: 0.8,
|
|
294
|
+
impact: "high",
|
|
295
|
+
headlines: "AWS wins major government contract"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@kb.fact :earnings, {
|
|
299
|
+
symbol: "AMZN",
|
|
300
|
+
days_until: 1,
|
|
301
|
+
expected_move: 6.2
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@kb.fact :options, {
|
|
305
|
+
symbol: "AMZN",
|
|
306
|
+
iv: 52.1,
|
|
307
|
+
iv_rank: 85
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@kb.fact :market, { trend: "bullish" }
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
if __FILE__ == $0
|
|
316
|
+
demo = TradingDemo.new
|
|
317
|
+
demo.demo_scenarios
|
|
318
|
+
|
|
319
|
+
puts "\n" + "="*60
|
|
320
|
+
puts "TRADING SYSTEM DEMONSTRATION COMPLETE"
|
|
321
|
+
puts "="*60
|
|
322
|
+
end
|