razvan commited on
Commit
eff7121
Β·
verified Β·
1 Parent(s): 792b2a2

Upload builderbrain/reasoning_agent.py

Browse files
Files changed (1) hide show
  1. builderbrain/reasoning_agent.py +401 -0
builderbrain/reasoning_agent.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Reasoning Agent + Trade Signal Generation
3
+ =========================================
4
+
5
+ Produces structured reasoning traces for each trade recommendation.
6
+ Each trace is:
7
+ - Hashed and anchored on-chain as an artifact
8
+ - Contains: data sources, argument structure, risk factors, confidence
9
+ - Links to builder code execution for auditability
10
+
11
+ This is "Trading-R1" β€” reasoning as a first-class product.
12
+ """
13
+
14
+ import hashlib
15
+ import json
16
+ import time
17
+ from dataclasses import dataclass, asdict
18
+ from typing import List, Dict, Optional, Any
19
+ from datetime import datetime
20
+ import uuid
21
+
22
+
23
+ @dataclass
24
+ class DataSource:
25
+ """A piece of evidence used in reasoning."""
26
+ source_type: str # 'polymarket', 'news', 'social', 'onchain', 'model'
27
+ source_id: str # URL, API endpoint, tweet ID, etc.
28
+ timestamp: str
29
+ data_summary: str
30
+ relevance_score: float # 0-1
31
+ raw_data: Optional[Dict] = None
32
+
33
+
34
+ @dataclass
35
+ class Argument:
36
+ """A structured argument for a position."""
37
+ claim: str
38
+ evidence: List[str] # source_ids
39
+ strength: float # 0-1
40
+ direction: str # 'bullish', 'bearish', 'neutral'
41
+ confidence: float # 0-1
42
+
43
+
44
+ @dataclass
45
+ class RiskFactor:
46
+ """A risk that could invalidate the thesis."""
47
+ description: str
48
+ probability: float # 0-1
49
+ impact: str # 'low', 'medium', 'high', 'catastrophic'
50
+ mitigation: str
51
+
52
+
53
+ @dataclass
54
+ class ReasoningTrace:
55
+ """
56
+ A complete reasoning artifact for a trade recommendation.
57
+
58
+ Anchored on-chain via hash for auditability.
59
+ """
60
+ trace_id: str
61
+ market_id: str
62
+ market_title: str
63
+ side: str # 'YES' or 'NO'
64
+ timestamp: str
65
+
66
+ # Core reasoning
67
+ model_probability: float
68
+ market_probability: float
69
+ edge: float
70
+
71
+ # Components
72
+ data_sources: List[DataSource]
73
+ arguments: List[Argument]
74
+ risk_factors: List[RiskFactor]
75
+
76
+ # Meta
77
+ agent_version: str
78
+ confidence: float # composite 0-1
79
+ reasoning_hash: str # SHA256 of canonical JSON
80
+
81
+ # Execution link
82
+ builder_code: Optional[str] = None
83
+ executed: bool = False
84
+ execution_tx: Optional[str] = None
85
+
86
+
87
+ @dataclass
88
+ class TradeSignal:
89
+ """A complete trade recommendation with reasoning."""
90
+ market_id: str
91
+ side: str
92
+ size_fraction: float # of bankroll
93
+ expected_return: float
94
+ confidence: float
95
+ reasoning_trace: ReasoningTrace
96
+ urgency: str # 'immediate', '24h', 'week', 'pass'
97
+
98
+ def to_dict(self) -> Dict:
99
+ return asdict(self)
100
+
101
+
102
+ class ReasoningAgent:
103
+ """
104
+ Generates structured reasoning traces for prediction market trades.
105
+
106
+ Simulates the intelligence layer: ingesting data, forming beliefs,
107
+ articulating arguments, and quantifying risks.
108
+
109
+ In production, this would connect to live data feeds (news APIs,
110
+ social media, on-chain signals). For hackathon, we simulate with
111
+ structured inputs.
112
+ """
113
+
114
+ def __init__(self, agent_version: str = "builderbrain-v0.1"):
115
+ self.agent_version = agent_version
116
+ self.trace_history: List[ReasoningTrace] = []
117
+ self.knowledge_base: Dict[str, Any] = {}
118
+
119
+ # ────────────────────────────── Core Reasoning ──────────────────────────────
120
+
121
+ def reason_about_market(
122
+ self,
123
+ market_id: str,
124
+ market_title: str,
125
+ market_prob: float,
126
+ model_prob: float,
127
+ data_sources: List[Dict],
128
+ theme: str = "general",
129
+ ) -> ReasoningTrace:
130
+ """
131
+ Generate a complete reasoning trace for a market.
132
+
133
+ In production, this would:
134
+ 1. Scrape news/social for relevant signals
135
+ 2. Run NLP models for sentiment/entity extraction
136
+ 3. Cross-reference with historical market patterns
137
+ 4. Produce probability estimate with uncertainty
138
+
139
+ For hackathon, we simulate with structured inputs.
140
+ """
141
+ edge = model_prob - market_prob
142
+ side = "YES" if edge > 0 else "NO"
143
+
144
+ # Parse data sources
145
+ sources = [DataSource(**ds) for ds in data_sources]
146
+
147
+ # Generate arguments based on edge direction and theme
148
+ arguments = self._generate_arguments(
149
+ market_title, theme, edge, sources
150
+ )
151
+
152
+ # Generate risk factors
153
+ risks = self._generate_risks(market_title, theme, edge)
154
+
155
+ # Compute composite confidence
156
+ arg_confidence = max(
157
+ [a.confidence for a in arguments] + [0.5]
158
+ )
159
+ data_quality = min(1.0, len(sources) * 0.2 + 0.3)
160
+ confidence = arg_confidence * data_quality * min(abs(edge) * 5, 1.0)
161
+
162
+ # Build trace
163
+ trace = ReasoningTrace(
164
+ trace_id=f"trace_{uuid.uuid4().hex[:12]}",
165
+ market_id=market_id,
166
+ market_title=market_title,
167
+ side=side,
168
+ timestamp=datetime.utcnow().isoformat(),
169
+ model_probability=model_prob,
170
+ market_probability=market_prob,
171
+ edge=edge,
172
+ data_sources=sources,
173
+ arguments=arguments,
174
+ risk_factors=risks,
175
+ agent_version=self.agent_version,
176
+ confidence=round(confidence, 4),
177
+ reasoning_hash="", # computed below
178
+ )
179
+
180
+ # Compute hash
181
+ trace.reasoning_hash = self._hash_trace(trace)
182
+
183
+ self.trace_history.append(trace)
184
+ return trace
185
+
186
+ def _generate_arguments(
187
+ self,
188
+ title: str,
189
+ theme: str,
190
+ edge: float,
191
+ sources: List[DataSource],
192
+ ) -> List[Argument]:
193
+ """Generate structured arguments from market context."""
194
+ arguments = []
195
+
196
+ # Base argument from edge direction
197
+ if edge > 0:
198
+ arguments.append(Argument(
199
+ claim=f"Market underprices {title} by {abs(edge):.1%}",
200
+ evidence=[s.source_id for s in sources[:2]],
201
+ strength=min(abs(edge) * 3, 0.95),
202
+ direction="bullish" if edge > 0 else "bearish",
203
+ confidence=min(abs(edge) * 2, 0.9),
204
+ ))
205
+
206
+ # Theme-specific arguments
207
+ theme_args = self._theme_arguments(title, theme, edge, sources)
208
+ arguments.extend(theme_args)
209
+
210
+ return arguments
211
+
212
+ def _theme_arguments(
213
+ self,
214
+ title: str,
215
+ theme: str,
216
+ edge: float,
217
+ sources: List[DataSource],
218
+ ) -> List[Argument]:
219
+ """Generate theme-specific arguments."""
220
+ args = []
221
+
222
+ if theme == "politics":
223
+ args.append(Argument(
224
+ claim="Polling momentum and fundraising data support this direction",
225
+ evidence=[s.source_id for s in sources if s.source_type == "news"][:2],
226
+ strength=0.7,
227
+ direction="bullish" if edge > 0 else "bearish",
228
+ confidence=0.65,
229
+ ))
230
+
231
+ elif theme == "crypto":
232
+ args.append(Argument(
233
+ claim="On-chain flows and ETF momentum align with price direction",
234
+ evidence=[s.source_id for s in sources if s.source_type == "onchain"][:2],
235
+ strength=0.75,
236
+ direction="bullish" if edge > 0 else "bearish",
237
+ confidence=0.7,
238
+ ))
239
+
240
+ elif theme == "sports":
241
+ args.append(Argument(
242
+ claim="Injury reports and lineup data support this probability",
243
+ evidence=[s.source_id for s in sources if s.source_type == "news"][:2],
244
+ strength=0.6,
245
+ direction="bullish" if edge > 0 else "bearish",
246
+ confidence=0.55,
247
+ ))
248
+
249
+ elif theme == "macro":
250
+ args.append(Argument(
251
+ claim="Fed communications and economic prints support this direction",
252
+ evidence=[s.source_id for s in sources if s.source_type == "news"][:2],
253
+ strength=0.65,
254
+ direction="bullish" if edge > 0 else "bearish",
255
+ confidence=0.6,
256
+ ))
257
+
258
+ return args
259
+
260
+ def _generate_risks(
261
+ self,
262
+ title: str,
263
+ theme: str,
264
+ edge: float,
265
+ ) -> List[RiskFactor]:
266
+ """Generate risk factors for a market."""
267
+ risks = [
268
+ RiskFactor(
269
+ description="Black swan event invalidates base case",
270
+ probability=0.05,
271
+ impact="catastrophic",
272
+ mitigation="Position sizing limits + correlation caps",
273
+ ),
274
+ RiskFactor(
275
+ description="New information shifts probability before position closes",
276
+ probability=0.25,
277
+ impact="medium",
278
+ mitigation="Dynamic position updates + stop-loss on edge decay",
279
+ ),
280
+ RiskFactor(
281
+ description="Market manipulation or wash trading distorts price",
282
+ probability=0.1,
283
+ impact="high",
284
+ mitigation="Liquidity filters + cross-market validation",
285
+ ),
286
+ ]
287
+
288
+ if theme == "politics":
289
+ risks.append(RiskFactor(
290
+ description="Late-breaking scandal or debate performance shift",
291
+ probability=0.2,
292
+ impact="high",
293
+ mitigation="Reduce position 48h before major events",
294
+ ))
295
+
296
+ elif theme == "crypto":
297
+ risks.append(RiskFactor(
298
+ description="Regulatory action (SEC, exchange shutdown)",
299
+ probability=0.15,
300
+ impact="catastrophic",
301
+ mitigation="Diversify across uncorrelated tokens + max 10% per token",
302
+ ))
303
+
304
+ return risks
305
+
306
+ def _hash_trace(self, trace: ReasoningTrace) -> str:
307
+ """Compute SHA256 hash of canonical trace representation."""
308
+ # Create canonical JSON (sorted keys, no whitespace)
309
+ canonical = json.dumps({
310
+ "market_id": trace.market_id,
311
+ "side": trace.side,
312
+ "model_prob": trace.model_probability,
313
+ "market_prob": trace.market_probability,
314
+ "edge": trace.edge,
315
+ "arguments": [
316
+ {"claim": a.claim, "strength": a.strength, "confidence": a.confidence}
317
+ for a in trace.arguments
318
+ ],
319
+ "risks": [
320
+ {"desc": r.description, "prob": r.probability, "impact": r.impact}
321
+ for r in trace.risk_factors
322
+ ],
323
+ "timestamp": trace.timestamp,
324
+ }, sort_keys=True, separators=(',', ':'))
325
+
326
+ return hashlib.sha256(canonical.encode()).hexdigest()[:32]
327
+
328
+ # ────────────────────────────── Signal Generation ──────────────────────────────
329
+
330
+ def generate_signal(
331
+ self,
332
+ trace: ReasoningTrace,
333
+ kelly_fraction: float,
334
+ expected_return: float,
335
+ ) -> TradeSignal:
336
+ """Convert reasoning trace to executable trade signal."""
337
+ # Determine urgency based on edge magnitude and time to expiry
338
+ abs_edge = abs(trace.edge)
339
+ if abs_edge > 0.15:
340
+ urgency = "immediate"
341
+ elif abs_edge > 0.08:
342
+ urgency = "24h"
343
+ elif abs_edge > 0.03:
344
+ urgency = "week"
345
+ else:
346
+ urgency = "pass"
347
+
348
+ return TradeSignal(
349
+ market_id=trace.market_id,
350
+ side=trace.side,
351
+ size_fraction=kelly_fraction,
352
+ expected_return=expected_return,
353
+ confidence=trace.confidence,
354
+ reasoning_trace=trace,
355
+ urgency=urgency,
356
+ )
357
+
358
+ # ────────────────────────────── Trace Retrieval ──────────────────────────────
359
+
360
+ def get_trace(self, trace_id: str) -> Optional[ReasoningTrace]:
361
+ """Retrieve a trace by ID."""
362
+ for t in self.trace_history:
363
+ if t.trace_id == trace_id:
364
+ return t
365
+ return None
366
+
367
+ def get_traces_for_market(self, market_id: str) -> List[ReasoningTrace]:
368
+ """Get all traces for a market."""
369
+ return [t for t in self.trace_history if t.market_id == market_id]
370
+
371
+ def get_top_traces(
372
+ self,
373
+ min_confidence: float = 0.6,
374
+ limit: int = 20,
375
+ ) -> List[ReasoningTrace]:
376
+ """Get highest-confidence traces."""
377
+ filtered = [t for t in self.trace_history if t.confidence >= min_confidence]
378
+ filtered.sort(key=lambda t: t.confidence, reverse=True)
379
+ return filtered[:limit]
380
+
381
+ def export_traces(self, filepath: str):
382
+ """Export all traces to JSON for audit."""
383
+ data = [asdict(t) for t in self.trace_history]
384
+ with open(filepath, 'w') as f:
385
+ json.dump(data, f, indent=2, default=str)
386
+
387
+ def stats(self) -> Dict:
388
+ """Agent performance statistics."""
389
+ if not self.trace_history:
390
+ return {}
391
+
392
+ edges = [t.edge for t in self.trace_history]
393
+ confidences = [t.confidence for t in self.trace_history]
394
+
395
+ return {
396
+ "total_traces": len(self.trace_history),
397
+ "avg_edge": sum(edges) / len(edges),
398
+ "avg_confidence": sum(confidences) / len(confidences),
399
+ "high_confidence_traces": sum(1 for c in confidences if c > 0.7),
400
+ "themes": list(set(t.market_id.split('_')[0] for t in self.trace_history)),
401
+ }