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

Commit ab4ef92

Browse files
author
TheophilusChinomona
committed
fix: production hardening — crash bugs, security, logging, tests
CRITICAL BUG FIXES: - Fix infinite recursion on error in trade.py/creator.py (tenacity retry w/ backoff) - Fix cron.py Scheduler import shadowing (class overwrites import → stack overflow) - Fix duplicate prompts_polymarket method in prompts.py - Fix duplicate 'restricted' field in SimpleEvent model - Remove pdb.set_trace() from polymarket.py - Fix search.py executing API call at module import time SECURITY: - Validate POLYGON_WALLET_PRIVATE_KEY at init (fail fast) - Harden format_trade_prompt_for_execution: bounds check (0,1], input validation - Replace all bare 'except:pass' with specific exception types OPERATIONAL: - Add 30s HTTP timeouts to all httpx calls - Add MAX_PAGINATION_ITERATIONS cap (100) to get_all_current_markets - Replace all print() with structured logging - Add .dockerignore, slim Dockerfile with layer caching TESTING (0 → 47 tests): - test_objects.py: Pydantic model validation (10) - test_utils.py: preprocessing functions (8) - test_prompts.py: prompt generation (10) - test_executor.py: trade parsing + retain_keys (11) - test_gamma.py: GammaMarketClient parsing (8) CI: black --check, ruff lint, pytest (split lint+test jobs)
1 parent 081f2b5 commit ab4ef92

20 files changed

Lines changed: 865 additions & 229 deletions

.dockerignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.git
2+
.gitignore
3+
.github
4+
.venv
5+
__pycache__
6+
*.pyc
7+
*.pyo
8+
.env
9+
.env.*
10+
!.env.example
11+
tests/
12+
docs/images/
13+
*.md
14+
!README.md
15+
.pre-commit-config.yaml

.github/workflows/python-app.yml

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,27 @@ permissions:
1313
contents: read
1414

1515
jobs:
16-
build:
16+
lint:
17+
runs-on: ubuntu-latest
1718

19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: Set up Python 3.9
22+
uses: actions/setup-python@v3
23+
with:
24+
python-version: "3.9"
25+
- name: Install lint tools
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install black ruff
29+
- name: Lint with black (check only)
30+
run: |
31+
black --check --diff .
32+
- name: Lint with ruff
33+
run: |
34+
ruff check .
35+
36+
test:
1837
runs-on: ubuntu-latest
1938

2039
steps:
@@ -26,11 +45,8 @@ jobs:
2645
- name: Install dependencies
2746
run: |
2847
python -m pip install --upgrade pip
29-
pip install black pytest
48+
pip install pytest
3049
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31-
- name: Lint with black
32-
run: |
33-
black .
34-
- name: Test with unittest
50+
- name: Run tests
3551
run: |
36-
python -m unittest discover
52+
python -m pytest tests/ -v

Dockerfile

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
FROM python:3.9
1+
FROM python:3.9-slim
22

3-
COPY . /home
4-
WORKDIR /home
3+
WORKDIR /app
54

6-
RUN pip3 install -r requirements.txt
5+
# Install dependencies first for layer caching
6+
COPY requirements.txt .
7+
RUN pip install --no-cache-dir -r requirements.txt
8+
9+
# Copy application code
10+
COPY agents/ agents/
11+
COPY scripts/ scripts/
12+
13+
ENV PYTHONPATH="/app"
14+
15+
CMD ["python", "scripts/python/cli.py"]

agents/application/creator.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,52 @@
1+
import logging
2+
3+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
4+
15
from agents.application.executor import Executor as Agent
26
from agents.polymarket.gamma import GammaMarketClient as Gamma
37
from agents.polymarket.polymarket import Polymarket
48

9+
logger = logging.getLogger(__name__)
10+
11+
MAX_RETRIES = 3
12+
513

614
class Creator:
715
def __init__(self):
816
self.polymarket = Polymarket()
917
self.gamma = Gamma()
1018
self.agent = Agent()
1119

20+
@retry(
21+
stop=stop_after_attempt(MAX_RETRIES),
22+
wait=wait_exponential(multiplier=1, min=2, max=30),
23+
retry=retry_if_exception_type((ConnectionError, TimeoutError, RuntimeError)),
24+
reraise=True,
25+
)
1226
def one_best_market(self):
1327
"""
14-
15-
one_best_trade is a strategy that evaluates all events, markets, and orderbooks
16-
17-
leverages all available information sources accessible to the autonomous agent
18-
19-
then executes that trade without any human intervention
20-
28+
Evaluates all events, markets, and orderbooks using the autonomous agent,
29+
then proposes a new market idea.
2130
"""
22-
try:
23-
events = self.polymarket.get_all_tradeable_events()
24-
print(f"1. FOUND {len(events)} EVENTS")
31+
events = self.polymarket.get_all_tradeable_events()
32+
logger.info("1. FOUND %d EVENTS", len(events))
2533

26-
filtered_events = self.agent.filter_events_with_rag(events)
27-
print(f"2. FILTERED {len(filtered_events)} EVENTS")
34+
filtered_events = self.agent.filter_events_with_rag(events)
35+
logger.info("2. FILTERED %d EVENTS", len(filtered_events))
2836

29-
markets = self.agent.map_filtered_events_to_markets(filtered_events)
30-
print()
31-
print(f"3. FOUND {len(markets)} MARKETS")
37+
markets = self.agent.map_filtered_events_to_markets(filtered_events)
38+
logger.info("3. FOUND %d MARKETS", len(markets))
3239

33-
print()
34-
filtered_markets = self.agent.filter_markets(markets)
35-
print(f"4. FILTERED {len(filtered_markets)} MARKETS")
40+
filtered_markets = self.agent.filter_markets(markets)
41+
logger.info("4. FILTERED %d MARKETS", len(filtered_markets))
3642

37-
best_market = self.agent.source_best_market_to_create(filtered_markets)
38-
print(f"5. IDEA FOR NEW MARKET {best_market}")
39-
return best_market
43+
if not filtered_markets:
44+
logger.warning("No markets passed filtering — skipping")
45+
return None
4046

41-
except Exception as e:
42-
print(f"Error {e} \n \n Retrying")
43-
self.one_best_market()
47+
best_market = self.agent.source_best_market_to_create(filtered_markets)
48+
logger.info("5. IDEA FOR NEW MARKET %s", best_market)
49+
return best_market
4450

4551
def maintain_positions(self):
4652
pass

agents/application/cron.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1-
from agents.application.trade import Trader
2-
1+
import logging
32
import time
43

5-
from scheduler import Scheduler
4+
from scheduler import Scheduler as TimeScheduler
65
from scheduler.trigger import Monday
76

7+
from agents.application.trade import Trader
8+
9+
logger = logging.getLogger(__name__)
10+
811

9-
class Scheduler:
12+
class TradingScheduler:
1013
def __init__(self) -> None:
1114
self.trader = Trader()
12-
self.schedule = Scheduler()
15+
self.schedule = TimeScheduler()
1316

1417
def start(self) -> None:
18+
logger.info("Starting trading scheduler loop")
1519
while True:
1620
self.schedule.exec_jobs()
1721
time.sleep(1)
1822

1923

20-
class TradingAgent(Scheduler):
24+
class TradingAgent(TradingScheduler):
2125
def __init__(self) -> None:
22-
super()
23-
self.trader = Trader()
26+
super().__init__()
2427
self.weekly(Monday(), self.trader.one_best_trade)

agents/application/executor.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import os
2-
import json
31
import ast
2+
import json
3+
import logging
4+
import math
5+
import os
46
import re
57
from typing import List, Dict, Any
68

7-
import math
8-
99
from dotenv import load_dotenv
1010
from langchain_core.messages import HumanMessage, SystemMessage
1111
from langchain_openai import ChatOpenAI
@@ -16,6 +16,8 @@
1616
from agents.application.prompts import Prompter
1717
from agents.polymarket.polymarket import Polymarket
1818

19+
logger = logging.getLogger(__name__)
20+
1921
def retain_keys(data, keys_to_retain):
2022
if isinstance(data, dict):
2123
return {
@@ -129,9 +131,7 @@ def filter_events(self, events: "list[SimpleEvent]") -> str:
129131

130132
def filter_events_with_rag(self, events: "list[SimpleEvent]") -> str:
131133
prompt = self.prompter.filter_events()
132-
print()
133-
print("... prompting ... ", prompt)
134-
print()
134+
logger.info("... prompting ... %s", prompt)
135135
return self.chroma.events(events, prompt)
136136

137137
def map_filtered_events_to_markets(
@@ -149,9 +149,7 @@ def map_filtered_events_to_markets(
149149

150150
def filter_markets(self, markets) -> "list[tuple]":
151151
prompt = self.prompter.filter_markets()
152-
print()
153-
print("... prompting ... ", prompt)
154-
print()
152+
logger.info("... prompting ... %s", prompt)
155153
return self.chroma.markets(markets, prompt)
156154

157155
def source_best_trade(self, market_object) -> str:
@@ -163,36 +161,55 @@ def source_best_trade(self, market_object) -> str:
163161
description = market_document["page_content"]
164162

165163
prompt = self.prompter.superforecaster(question, description, outcomes)
166-
print()
167-
print("... prompting ... ", prompt)
168-
print()
164+
logger.info("... prompting superforecaster: %s", prompt)
169165
result = self.llm.invoke(prompt)
170166
content = result.content
167+
logger.info("Superforecaster result: %s", content)
171168

172-
print("result: ", content)
173-
print()
174169
prompt = self.prompter.one_best_trade(content, outcomes, outcome_prices)
175-
print("... prompting ... ", prompt)
176-
print()
170+
logger.info("... prompting trade: %s", prompt)
177171
result = self.llm.invoke(prompt)
178172
content = result.content
179-
180-
print("result: ", content)
181-
print()
173+
logger.info("Trade result: %s", content)
182174
return content
183175

184176
def format_trade_prompt_for_execution(self, best_trade: str) -> float:
177+
"""Parse LLM trade output into a safe USDC amount.
178+
179+
Expected format: 'price:0.5, size:0.1, side:BUY,'
180+
Returns: size_fraction * usdc_balance
181+
"""
185182
data = best_trade.split(",")
186-
# price = re.findall("\d+\.\d+", data[0])[0]
187-
size = re.findall("\d+\.\d+", data[1])[0]
183+
if len(data) < 2:
184+
raise ValueError(
185+
f"Trade output has unexpected format (need >=2 comma-separated parts): {best_trade!r}"
186+
)
187+
188+
size_matches = re.findall(r"\d+\.?\d*", data[1])
189+
if not size_matches:
190+
raise ValueError(
191+
f"Could not extract size from trade output: {data[1]!r}"
192+
)
193+
194+
size = float(size_matches[0])
195+
if not (0 < size <= 1):
196+
raise ValueError(
197+
f"Trade size {size} out of safe range (0, 1] — refusing to execute"
198+
)
199+
188200
usdc_balance = self.polymarket.get_usdc_balance()
189-
return float(size) * usdc_balance
201+
amount = size * usdc_balance
202+
logger.info(
203+
"Trade size fraction: %.4f, USDC balance: %.2f, order amount: %.2f",
204+
size,
205+
usdc_balance,
206+
amount,
207+
)
208+
return amount
190209

191210
def source_best_market_to_create(self, filtered_markets) -> str:
192211
prompt = self.prompter.create_new_market(filtered_markets)
193-
print()
194-
print("... prompting ... ", prompt)
195-
print()
212+
logger.info("... prompting market creation: %s", prompt)
196213
result = self.llm.invoke(prompt)
197214
content = result.content
198215
return content

agents/application/prompts.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,6 @@ def sentiment_analyzer(self, question: str, outcome: str) -> float:
3333
3434
"""
3535

36-
def prompts_polymarket(
37-
self, data1: str, data2: str, market_question: str, outcome: str
38-
) -> str:
39-
current_market_data = str(data1)
40-
current_event_data = str(data2)
41-
return f"""
42-
You are an AI assistant for users of a prediction market called Polymarket.
43-
Users want to place bets based on their beliefs of market outcomes such as political or sports events.
44-
45-
Here is data for current Polymarket markets {current_market_data} and
46-
current Polymarket events {current_event_data}.
47-
48-
Help users identify markets to trade based on their interests or queries.
49-
Provide specific information for markets including probabilities of outcomes.
50-
Give your response in the following format:
51-
52-
I believe {market_question} has a likelihood {float} for outcome of {outcome}.
53-
"""
54-
5536
def prompts_polymarket(self, data1: str, data2: str) -> str:
5637
current_market_data = str(data1)
5738
current_event_data = str(data2)

0 commit comments

Comments
 (0)