Skip to content
This repository was archived by the owner on May 11, 2026. It is now read-only.

Commit f292ed9

Browse files
author
TheophilusChinomona
committed
fix: lazy CLOB client init + paper trading script
- Polymarket.__init__: make CLOB client lazy via _ensure_client() Read-only operations (get_all_events, get_all_markets, etc.) no longer need POLYGON_WALLET_PRIVATE_KEY. Trading operations (execute_order, get_orderbook, etc.) call _ensure_client() which validates the key. - paper_trade.py: end-to-end paper trading demo that: - Fetches live events from Gamma API - Fetches and analyzes market details with live prices - Simulates a paper trade with edge detection - Reports pipeline readiness (which keys are set/missing) - No real trades executed
1 parent 1bbf34c commit f292ed9

2 files changed

Lines changed: 227 additions & 2 deletions

File tree

agents/polymarket/polymarket.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ def __init__(self) -> None:
6767
address=self.ctf_address, abi=self.erc1155_set_approval
6868
)
6969

70-
self._init_api_keys()
7170
self._init_approvals(False)
71+
self._client_initialized = False
7272

73-
def _init_api_keys(self) -> None:
73+
def _ensure_client(self) -> None:
74+
"""Lazily initialize CLOB client only when trading operations are needed."""
75+
if self._client_initialized:
76+
return
7477
if not self.private_key:
7578
raise ValueError(
7679
"POLYGON_WALLET_PRIVATE_KEY is required for trading operations"
@@ -80,6 +83,7 @@ def _init_api_keys(self) -> None:
8083
)
8184
self.credentials = self.client.create_or_derive_api_creds()
8285
self.client.set_api_creds(self.credentials)
86+
self._client_initialized = True
8387

8488
def _init_approvals(self, run: bool = False) -> None:
8589
if not run:
@@ -288,6 +292,7 @@ def get_all_tradeable_events(self) -> "list[SimpleEvent]":
288292
return self.filter_events_for_trading(all_events)
289293

290294
def get_sampling_simplified_markets(self) -> "list[SimpleEvent]":
295+
self._ensure_client()
291296
markets = []
292297
raw_sampling_simplified_markets = self.client.get_sampling_simplified_markets()
293298
for raw_market in raw_sampling_simplified_markets["data"]:
@@ -297,9 +302,11 @@ def get_sampling_simplified_markets(self) -> "list[SimpleEvent]":
297302
return markets
298303

299304
def get_orderbook(self, token_id: str) -> OrderBookSummary:
305+
self._ensure_client()
300306
return self.client.get_order_book(token_id)
301307

302308
def get_orderbook_price(self, token_id: str) -> float:
309+
self._ensure_client()
303310
return float(self.client.get_price(token_id))
304311

305312
def get_address_for_private_key(self):
@@ -335,11 +342,13 @@ def build_order(
335342
return order
336343

337344
def execute_order(self, price, size, side, token_id) -> str:
345+
self._ensure_client()
338346
return self.client.create_and_post_order(
339347
OrderArgs(price=price, size=size, side=side, token_id=token_id)
340348
)
341349

342350
def execute_market_order(self, market, amount) -> str:
351+
self._ensure_client()
343352
token_id = ast.literal_eval(market[0].dict()["metadata"]["clob_token_ids"])[1]
344353
order_args = MarketOrderArgs(
345354
token_id=token_id,

paper_trade.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
"""
2+
Paper trading v3 — demonstrates the full pipeline end-to-end.
3+
Relaxes the restricted filter for demo purposes since Polymarket
4+
geofences most events.
5+
"""
6+
7+
import logging
8+
import sys
9+
import os
10+
11+
logging.basicConfig(
12+
level=logging.INFO,
13+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
14+
handlers=[logging.StreamHandler(sys.stdout)],
15+
)
16+
logger = logging.getLogger("paper-trading")
17+
18+
sys.path.insert(0, os.path.dirname(__file__))
19+
20+
from agents.polymarket.gamma import GammaMarketClient
21+
from agents.polymarket.polymarket import Polymarket
22+
from agents.utils.objects import SimpleMarket, SimpleEvent
23+
24+
STEP_SEP = "=" * 70
25+
26+
27+
def step(n, title):
28+
logger.info("\n%s\nSTEP %d: %s\n%s", STEP_SEP, n, title, STEP_SEP)
29+
30+
31+
def main():
32+
logger.info("Starting paper trading run v3...\n")
33+
34+
gamma = GammaMarketClient()
35+
poly = Polymarket()
36+
37+
# ── Step 1: Fetch and filter events (relaxed for paper trading) ────
38+
step(1, "Fetching active events from Gamma API")
39+
40+
raw_events = gamma.get_current_events(limit=100)
41+
logger.info("✅ Fetched %d active events from Gamma API", len(raw_events))
42+
43+
# Parse through Polymarket model
44+
all_events = []
45+
for e in raw_events:
46+
try:
47+
ed = poly.map_api_to_event(e)
48+
all_events.append(SimpleEvent(**ed))
49+
except Exception as ex:
50+
logger.debug("Skipping event: %s", ex)
51+
52+
# Relaxed filter: active + not closed + not archived (skip restricted for demo)
53+
paper_tradeable = [
54+
e for e in all_events
55+
if e.active and not e.closed and not e.archived
56+
]
57+
logger.info("Events passing paper-trade filter: %d", len(paper_tradeable))
58+
59+
if paper_tradeable:
60+
logger.info("\nTop tradeable events:")
61+
for e in paper_tradeable[:10]:
62+
n_markets = len(e.markets.split(",")) if e.markets else 0
63+
logger.info(
64+
" - [%d] %s (%d markets, volume=%.0f)",
65+
e.id,
66+
e.title[:65],
67+
n_markets,
68+
0, # SimpleEvent doesn't have volume
69+
)
70+
71+
# ── Step 2: Fetch market details for top events ────────────────────
72+
step(2, "Fetching market details for top events")
73+
74+
markets_data = []
75+
for event in paper_tradeable[:5]:
76+
if not event.markets:
77+
continue
78+
market_ids = event.markets.split(",")[:2] # Max 2 per event
79+
for mid in market_ids:
80+
mid = mid.strip()
81+
if not mid:
82+
continue
83+
try:
84+
raw = gamma.get_market(mid)
85+
m = poly.map_api_to_market(raw)
86+
markets_data.append(m)
87+
except Exception as ex:
88+
logger.debug("Skipping market %s: %s", mid, ex)
89+
90+
logger.info("✅ Fetched %d market details", len(markets_data))
91+
92+
# ── Step 3: Display market analysis ────────────────────────────────
93+
step(3, "Market analysis (what the agent evaluates)")
94+
95+
for i, m in enumerate(markets_data[:10]):
96+
question = m.get("question", "N/A")
97+
outcomes = m.get("outcomes", "N/A")
98+
prices = m.get("outcome_prices", "N/A")
99+
active = m.get("active", False)
100+
funded = m.get("funded", False)
101+
spread = m.get("spread", "N/A")
102+
103+
# Parse outcome prices for analysis
104+
try:
105+
price_list = eval(prices) if isinstance(prices, str) else prices
106+
yes_price = float(price_list[0]) if price_list else 0
107+
no_price = float(price_list[1]) if len(price_list) > 1 else 0
108+
except:
109+
yes_price = 0
110+
no_price = 0
111+
112+
# Simple edge detection
113+
logger.info(
114+
"\n 📊 Market %d: %s\n"
115+
" Outcomes: %s\n"
116+
" Current prices: Yes=%.3f (%.1f%%) | No=%.3f (%.1f%%)\n"
117+
" Active: %s | Funded: %s | Spread: %s\n"
118+
" 💡 Agent would run Superforecaster prompt to assess if "
119+
"the market is mispriced vs its base rate",
120+
i + 1,
121+
question[:80],
122+
outcomes,
123+
yes_price, yes_price * 100,
124+
no_price, no_price * 100,
125+
active, funded, spread,
126+
)
127+
128+
# ── Step 4: What a paper trade would look like ─────────────────────
129+
step(4, "Paper trade example (simulated)")
130+
131+
if markets_data:
132+
m = markets_data[0]
133+
question = m.get("question", "N/A")
134+
prices = m.get("outcome_prices", "['0.5','0.5']")
135+
136+
try:
137+
price_list = eval(prices) if isinstance(prices, str) else prices
138+
yes_price = float(price_list[0])
139+
except:
140+
yes_price = 0.5
141+
142+
# Simulate what the agent would decide
143+
simulated_prediction = 0.6 # Agent thinks 60% chance of Yes
144+
edge = simulated_prediction - yes_price
145+
146+
if edge > 0.05:
147+
action = "BUY"
148+
size = min(0.1, edge) # Scale size by edge
149+
reasoning = f"Market underprices YES at {yes_price:.1%}, agent predicts {simulated_prediction:.0%}"
150+
elif edge < -0.05:
151+
action = "SELL"
152+
size = min(0.1, abs(edge))
153+
reasoning = f"Market overprices YES at {yes_price:.1%}, agent predicts {simulated_prediction:.0%}"
154+
else:
155+
action = "HOLD"
156+
size = 0
157+
reasoning = f"Market fairly priced at {yes_price:.1%}, agent predicts {simulated_prediction:.0%}"
158+
159+
logger.info(
160+
"\n 🎯 Simulated paper trade:\n"
161+
" Market: %s\n"
162+
" Current Yes price: %.3f (%.1f%%)\n"
163+
" Agent prediction: %.0f%%\n"
164+
" Edge: %+.1f%%\n"
165+
" Action: %s\n"
166+
" Size: %.1f%% of portfolio\n"
167+
" Reasoning: %s\n"
168+
"\n 🔒 This is a SIMULATION — no real trade executed.",
169+
question[:80],
170+
yes_price, yes_price * 100,
171+
simulated_prediction * 100,
172+
edge * 100,
173+
action,
174+
size * 100,
175+
reasoning,
176+
)
177+
178+
# ── Step 5: Pipeline status ────────────────────────────────────────
179+
step(5, "Pipeline readiness")
180+
181+
openai_key = os.getenv("OPENAI_API_KEY")
182+
poly_key = os.getenv("POLYGON_WALLET_PRIVATE_KEY")
183+
184+
logger.info(
185+
"\n Environment:\n"
186+
" OPENAI_API_KEY: %s\n"
187+
" POLYGON_WALLET_PRIVATE_KEY: %s\n"
188+
"\n Pipeline stages completed:\n"
189+
" ✅ Step 1: Fetch events from Gamma API\n"
190+
" ✅ Step 2: Fetch market details\n"
191+
" ✅ Step 3: Display market analysis\n"
192+
" ✅ Step 4: Simulate paper trade\n"
193+
"\n Pipeline stages requiring OPENAI_API_KEY:\n"
194+
" %s RAG filter events (ChromaDB + embeddings)\n"
195+
" %s RAG filter markets\n"
196+
" %s Superforecaster LLM prediction\n"
197+
" %s Trade decision generation\n"
198+
"\n Pipeline stages requiring POLYGON_WALLET_PRIVATE_KEY:\n"
199+
" %s USDC balance check\n"
200+
" %s Order signing and execution\n"
201+
"\n Trade execution: 🔒 DISABLED (paper trading mode)",
202+
"✅ Set" if openai_key else "❌ Not set",
203+
"✅ Set" if poly_key else "❌ Not set",
204+
"✅" if openai_key else "⏭️",
205+
"✅" if openai_key else "⏭️",
206+
"✅" if openai_key else "⏭️",
207+
"✅" if openai_key else "⏭️",
208+
"✅" if poly_key else "⏭️",
209+
"✅" if poly_key else "⏭️",
210+
)
211+
212+
logger.info("\n%s\nPaper trading run complete. 0 real trades. 0 USDC risked.\n%s\n", STEP_SEP, STEP_SEP)
213+
214+
215+
if __name__ == "__main__":
216+
main()

0 commit comments

Comments
 (0)