razvan commited on
Commit
b2e4873
Β·
verified Β·
1 Parent(s): 7a2cfcb

Upload builderbrain/pipeline.py

Browse files
Files changed (1) hide show
  1. builderbrain/pipeline.py +377 -0
builderbrain/pipeline.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BuilderBrain Main Pipeline
3
+ ==========================
4
+
5
+ End-to-end flow:
6
+ 1. Ingest prediction market data (Polymarket + others)
7
+ 2. Generate reasoning traces for each market
8
+ 3. Compute correlation-aware Kelly positions
9
+ 4. Route orders via builder codes
10
+ 5. Settle via Arc (Gateway, Nanopayments, USYC)
11
+ 6. Log reasoning traces on-chain as artifacts
12
+
13
+ Usage:
14
+ brain = BuilderBrain(bankroll_usd=10000)
15
+ brain.run_cycle()
16
+ signals = brain.get_signals()
17
+ """
18
+
19
+ import json
20
+ from typing import List, Dict, Optional
21
+ from datetime import datetime
22
+
23
+ from .quant_engine import KellyEngine, MarketEdge, Position
24
+ from .polymarket_client import PolymarketClient, BuilderCodeRouter, PolymarketMarket
25
+ from .reasoning_agent import ReasoningAgent, TradeSignal, ReasoningTrace
26
+ from .arc_bridge import ArcBridge
27
+
28
+
29
+ class BuilderBrain:
30
+ """
31
+ Main orchestrator for the BuilderBrain agent.
32
+
33
+ Combines:
34
+ - Quant engine (Kelly sizing, correlation matrix)
35
+ - Reasoning agent (structured argumentation, risk assessment)
36
+ - Polymarket client (market data, builder code routing)
37
+ - Arc bridge (settlement, nanopayments, USYC)
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ bankroll_usd: float = 10000.0,
43
+ paper_trade: bool = True,
44
+ builder_code: str = "builderbrain_default",
45
+ min_edge: float = 0.03,
46
+ max_positions: int = 20,
47
+ ):
48
+ self.bankroll = bankroll_usd
49
+ self.paper_trade = paper_trade
50
+ self.max_positions = max_positions
51
+
52
+ # Components
53
+ self.quant = KellyEngine(
54
+ bankroll_usd=bankroll_usd,
55
+ min_edge=min_edge,
56
+ )
57
+ self.reasoning = ReasoningAgent()
58
+ self.polymarket = PolymarketClient(paper_trade=paper_trade)
59
+ self.router = BuilderCodeRouter(self.polymarket)
60
+ self.arc = ArcBridge()
61
+
62
+ # Register default builder code
63
+ self.polymarket.register_builder_code(
64
+ code=builder_code,
65
+ name="BuilderBrain Intelligence",
66
+ description="AI-generated prediction market intelligence with Kelly sizing",
67
+ fee_share_bps=10,
68
+ )
69
+ self.default_builder_code = builder_code
70
+
71
+ # State
72
+ self.signals: List[TradeSignal] = []
73
+ self.positions: List[Position] = []
74
+ self.cycle_count = 0
75
+ self.paper_pnl = 0.0
76
+
77
+ # ────────────────────────────── Main Cycle ──────────────────────────────
78
+
79
+ def run_cycle(self, category: Optional[str] = None) -> List[TradeSignal]:
80
+ """
81
+ Execute one full intelligence β†’ sizing β†’ routing β†’ settlement cycle.
82
+
83
+ Returns list of trade signals generated.
84
+ """
85
+ print(f"\n{'='*60}")
86
+ print(f"BuilderBrain Cycle #{self.cycle_count + 1}")
87
+ print(f"{'='*60}")
88
+
89
+ # 1. Fetch markets
90
+ markets = self._fetch_markets(category)
91
+ if not markets:
92
+ print("[BuilderBrain] No markets fetched")
93
+ return []
94
+
95
+ print(f"[BuilderBrain] Fetched {len(markets)} markets")
96
+
97
+ # 2. Generate edges + reasoning
98
+ edges = self._generate_edges(markets)
99
+ print(f"[BuilderBrain] Generated {len(edges)} viable edges")
100
+
101
+ # 3. Kelly sizing
102
+ positions = self.quant.size_positions(edges)
103
+ print(f"[BuilderBrain] Sized {len(positions)} positions")
104
+
105
+ # 4. Route orders
106
+ signals = self._route_positions(positions)
107
+ print(f"[BuilderBrain] Generated {len(signals)} trade signals")
108
+
109
+ # 5. Settle via Arc
110
+ self._settle_signals(signals)
111
+
112
+ # 6. Update state
113
+ self.signals.extend(signals)
114
+ self.positions.extend(positions)
115
+ self.cycle_count += 1
116
+
117
+ return signals
118
+
119
+ def _fetch_markets(self, category: Optional[str]) -> List[PolymarketMarket]:
120
+ """Fetch live markets from Polymarket."""
121
+ return self.polymarket.fetch_markets(
122
+ active=True,
123
+ limit=50,
124
+ category=category,
125
+ )
126
+
127
+ def _generate_edges(self, markets: List[PolymarketMarket]) -> List[MarketEdge]:
128
+ """
129
+ Convert Polymarket markets to MarketEdge objects with model probabilities.
130
+
131
+ In production, this would:
132
+ - Run NLP models on news/social
133
+ - Query prediction models
134
+ - Cross-reference with historical patterns
135
+
136
+ For hackathon, we simulate model probabilities with structured logic.
137
+ """
138
+ edges = []
139
+
140
+ for m in markets:
141
+ # Simulate model probability based on market characteristics
142
+ # In reality, this comes from your quant models
143
+ model_prob = self._simulate_model_prob(m)
144
+
145
+ # Determine which side has edge
146
+ yes_edge = model_prob - m.implied_yes_prob
147
+ no_edge = (1 - model_prob) - m.best_no_price
148
+
149
+ # Take the side with larger edge
150
+ if yes_edge > abs(no_edge):
151
+ side = "YES"
152
+ edge = yes_edge
153
+ market_prob = m.implied_yes_prob
154
+ else:
155
+ side = "NO"
156
+ edge = no_edge
157
+ market_prob = m.best_no_price
158
+
159
+ # Skip if edge too small
160
+ if abs(edge) < self.quant.min_edge:
161
+ continue
162
+
163
+ # Determine theme from category
164
+ theme = self._categorize_theme(m.category, m.question)
165
+
166
+ edges.append(MarketEdge(
167
+ market_id=m.market_id,
168
+ title=m.question,
169
+ theme=theme,
170
+ side=side,
171
+ edge=edge,
172
+ market_prob=market_prob,
173
+ model_prob=model_prob if side == "YES" else 1 - model_prob,
174
+ liquidity_usd=m.liquidity,
175
+ expires_at=m.end_date,
176
+ ))
177
+
178
+ return edges
179
+
180
+ def _simulate_model_prob(self, market: PolymarketMarket) -> float:
181
+ """
182
+ Simulate a model probability for a market.
183
+
184
+ In production, this queries your actual prediction models.
185
+ For hackathon demo, we add structured noise to market price
186
+ to simulate "edge detection."
187
+ """
188
+ import random
189
+
190
+ # Base: market price is ~efficient, but we find small edges
191
+ base = market.implied_yes_prob
192
+
193
+ # Add structured noise based on market characteristics
194
+ # More liquid markets = less edge (more efficient)
195
+ liquidity_factor = 1 / (1 + market.liquidity / 100000)
196
+
197
+ # Volatility factor: high spread = more uncertainty = more edge potential
198
+ spread_factor = market.spread * 2
199
+
200
+ # Simulate edge: Β±5-15% on illiquid markets, Β±2-5% on liquid
201
+ noise = random.gauss(0, 0.03 * liquidity_factor + 0.01 * spread_factor)
202
+ noise = max(-0.15, min(0.15, noise)) # Cap extreme noise
203
+
204
+ model_prob = base + noise
205
+ return max(0.01, min(0.99, model_prob))
206
+
207
+ def _categorize_theme(self, category: str, question: str) -> str:
208
+ """Map market to theme block."""
209
+ cat_lower = category.lower()
210
+ q_lower = question.lower()
211
+
212
+ if any(w in q_lower for w in ['trump', 'election', 'biden', 'congress', 'vote']):
213
+ return 'politics'
214
+ elif any(w in q_lower for w in ['btc', 'bitcoin', 'eth', 'ethereum', 'crypto', 'etf']):
215
+ return 'crypto'
216
+ elif any(w in q_lower for w in ['super bowl', 'nba', 'world cup', 'champion']):
217
+ return 'sports'
218
+ elif any(w in q_lower for w in ['fed', 'cpi', 'recession', 'oil', 'rate']):
219
+ return 'macro'
220
+ elif 'politics' in cat_lower:
221
+ return 'politics'
222
+ elif 'crypto' in cat_lower:
223
+ return 'crypto'
224
+ elif 'sports' in cat_lower:
225
+ return 'sports'
226
+ else:
227
+ return 'other'
228
+
229
+ def _route_positions(self, positions: List[Position]) -> List[TradeSignal]:
230
+ """Route sized positions through reasoning + builder codes."""
231
+ signals = []
232
+
233
+ for pos in positions[:self.max_positions]:
234
+ # Generate reasoning trace
235
+ trace = self.reasoning.reason_about_market(
236
+ market_id=pos.market_id,
237
+ market_title=pos.market_id, # Would fetch actual title
238
+ market_prob=0.5, # Would fetch actual
239
+ model_prob=0.5 + pos.edge,
240
+ data_sources=[
241
+ {
242
+ "source_type": "polymarket",
243
+ "source_id": f"gamma/{pos.market_id}",
244
+ "timestamp": datetime.utcnow().isoformat(),
245
+ "data_summary": f"Market price: {0.5}, Our model: {0.5 + pos.edge}",
246
+ "relevance_score": 0.9,
247
+ }
248
+ ],
249
+ theme=self._categorize_theme("", pos.market_id),
250
+ )
251
+
252
+ # Generate trade signal
253
+ signal = self.reasoning.generate_signal(
254
+ trace=trace,
255
+ kelly_fraction=pos.fraction_of_bankroll,
256
+ expected_return=pos.expected_return,
257
+ )
258
+
259
+ # Link builder code
260
+ trace.builder_code = self.default_builder_code
261
+
262
+ # Route order via Polymarket
263
+ size_usd = pos.fraction_of_bankroll * self.bankroll
264
+
265
+ # Find market in polymarket client
266
+ pm_market = None
267
+ for m in self.polymarket.fetch_markets(limit=100):
268
+ if m.market_id == pos.market_id:
269
+ pm_market = m
270
+ break
271
+
272
+ if pm_market:
273
+ result = self.router.route_with_intent(
274
+ market=pm_market,
275
+ side="BUY",
276
+ outcome=pos.side,
277
+ size_usd=size_usd,
278
+ price=pm_market.best_yes_price if pos.side == "YES" else pm_market.best_no_price,
279
+ )
280
+
281
+ if result.get("status") == "FILLED":
282
+ trace.executed = True
283
+ trace.execution_tx = result.get("order_id")
284
+
285
+ signals.append(signal)
286
+
287
+ return signals
288
+
289
+ def _settle_signals(self, signals: List[TradeSignal]):
290
+ """Settle generated signals via Arc infrastructure."""
291
+ for signal in signals:
292
+ # Charge per-trade nanopayment
293
+ notional = signal.size_fraction * self.bankroll
294
+ self.arc.charge_trade_fee(
295
+ user_id="default_user",
296
+ trade_id=signal.reasoning_trace.trace_id,
297
+ notional_usd=notional,
298
+ )
299
+
300
+ # Charge per-insight fee
301
+ self.arc.charge_insight_fee(
302
+ user_id="default_user",
303
+ trace_id=signal.reasoning_trace.trace_id,
304
+ )
305
+
306
+ # Batch settle
307
+ if self.arc.pending_payments:
308
+ settlement = self.arc.batch_settle()
309
+ print(f"[Arc] Settled {settlement['settled']} payments = ${settlement['total_usd']:.4f}")
310
+
311
+ # ────────────────────────────── Queries ──────────────────────────────
312
+
313
+ def get_signals(self, min_confidence: float = 0.0) -> List[TradeSignal]:
314
+ """Get all generated trade signals."""
315
+ return [s for s in self.signals if s.confidence >= min_confidence]
316
+
317
+ def get_top_signals(self, n: int = 10) -> List[TradeSignal]:
318
+ """Get top N signals by expected return."""
319
+ sorted_signals = sorted(self.signals, key=lambda s: s.expected_return, reverse=True)
320
+ return sorted_signals[:n]
321
+
322
+ def get_portfolio_stats(self) -> Dict:
323
+ """Get current portfolio statistics."""
324
+ kelly_stats = self.quant.portfolio_stats(self.positions)
325
+ arc_stats = self.arc.stats()
326
+ reasoning_stats = self.reasoning.stats()
327
+
328
+ return {
329
+ "cycle": self.cycle_count,
330
+ "bankroll_usd": self.bankroll,
331
+ "kelly": kelly_stats,
332
+ "arc": arc_stats,
333
+ "reasoning": reasoning_stats,
334
+ "paper_portfolio": self.polymarket.get_paper_portfolio(),
335
+ "total_signals": len(self.signals),
336
+ "total_traces": len(self.reasoning.trace_history),
337
+ }
338
+
339
+ def export_audit_log(self, filepath: str = "builderbrain_audit.json"):
340
+ """Export complete audit log for on-chain anchoring."""
341
+ audit = {
342
+ "agent": "BuilderBrain",
343
+ "version": self.reasoning.agent_version,
344
+ "cycles": self.cycle_count,
345
+ "bankroll_usd": self.bankroll,
346
+ "signals": [
347
+ {
348
+ "market_id": s.market_id,
349
+ "side": s.side,
350
+ "size": s.size_fraction,
351
+ "expected_return": s.expected_return,
352
+ "confidence": s.confidence,
353
+ "urgency": s.urgency,
354
+ "trace_hash": s.reasoning_trace.reasoning_hash,
355
+ "executed": s.reasoning_trace.executed,
356
+ }
357
+ for s in self.signals
358
+ ],
359
+ "traces": [
360
+ {
361
+ "trace_id": t.trace_id,
362
+ "hash": t.reasoning_hash,
363
+ "market": t.market_title,
364
+ "edge": t.edge,
365
+ "confidence": t.confidence,
366
+ "arguments": len(t.arguments),
367
+ "risks": len(t.risk_factors),
368
+ }
369
+ for t in self.reasoning.trace_history
370
+ ],
371
+ "timestamp": datetime.utcnow().isoformat(),
372
+ }
373
+
374
+ with open(filepath, 'w') as f:
375
+ json.dump(audit, f, indent=2)
376
+
377
+ return filepath