Skip to content
This repository was archived by the owner on May 11, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git
.gitignore
.github
.venv
__pycache__
*.pyc
*.pyo
.env
.env.*
!.env.example
tests/
docs/images/
*.md
!README.md
.pre-commit-config.yaml
30 changes: 23 additions & 7 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,27 @@ permissions:
contents: read

jobs:
build:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python 3.9
uses: actions/setup-python@v3
with:
python-version: "3.9"
- name: Install lint tools
run: |
python -m pip install --upgrade pip
pip install black ruff
- name: Lint with black (check only)
run: |
black --check --diff .
- name: Lint with ruff
run: |
ruff check .

test:
runs-on: ubuntu-latest

steps:
Expand All @@ -26,11 +45,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black pytest
pip install pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with black
run: |
black .
- name: Test with unittest
- name: Run tests
run: |

Copilot AI Apr 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is running pytest directly against the source tree, but the added tests import agents.* and current CI output indicates ModuleNotFoundError: No module named 'agents'. Make the package importable in the workflow (e.g., export PYTHONPATH=$GITHUB_WORKSPACE, add a minimal packaging config and pip install -e ., and/or add agents/__init__.py).

Suggested change
run: |
run: |
export PYTHONPATH="$GITHUB_WORKSPACE"

Copilot uses AI. Check for mistakes.
python -m unittest discover
python -m pytest tests/ -v
17 changes: 13 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
FROM python:3.9
FROM python:3.9-slim

COPY . /home
WORKDIR /home
WORKDIR /app

RUN pip3 install -r requirements.txt
# Install dependencies first for layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY agents/ agents/
COPY scripts/ scripts/

ENV PYTHONPATH="/app"

CMD ["python", "scripts/python/cli.py"]
61 changes: 37 additions & 24 deletions agents/application/creator.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,59 @@
import httpx
import logging

from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type

from agents.application.executor import Executor as Agent
from agents.polymarket.gamma import GammaMarketClient as Gamma
from agents.polymarket.polymarket import Polymarket

logger = logging.getLogger(__name__)

MAX_RETRIES = 3


class Creator:
def __init__(self):
self.polymarket = Polymarket()
self.gamma = Gamma()
self.agent = Agent()

@retry(
stop=stop_after_attempt(MAX_RETRIES),
wait=wait_exponential(multiplier=1, min=2, max=30),
retry=retry_if_exception_type((
ConnectionError,
TimeoutError,
RuntimeError,
httpx.TimeoutException,
httpx.NetworkError,
)),
reraise=True,
)
def one_best_market(self):
"""

one_best_trade is a strategy that evaluates all events, markets, and orderbooks

leverages all available information sources accessible to the autonomous agent

then executes that trade without any human intervention

Evaluates all events, markets, and orderbooks using the autonomous agent,
then proposes a new market idea.
"""
try:
events = self.polymarket.get_all_tradeable_events()
print(f"1. FOUND {len(events)} EVENTS")
events = self.polymarket.get_all_tradeable_events()
logger.info("1. FOUND %d EVENTS", len(events))

filtered_events = self.agent.filter_events_with_rag(events)
print(f"2. FILTERED {len(filtered_events)} EVENTS")
filtered_events = self.agent.filter_events_with_rag(events)
logger.info("2. FILTERED %d EVENTS", len(filtered_events))

markets = self.agent.map_filtered_events_to_markets(filtered_events)
print()
print(f"3. FOUND {len(markets)} MARKETS")
markets = self.agent.map_filtered_events_to_markets(filtered_events)
logger.info("3. FOUND %d MARKETS", len(markets))

print()
filtered_markets = self.agent.filter_markets(markets)
print(f"4. FILTERED {len(filtered_markets)} MARKETS")
filtered_markets = self.agent.filter_markets(markets)
logger.info("4. FILTERED %d MARKETS", len(filtered_markets))

best_market = self.agent.source_best_market_to_create(filtered_markets)
print(f"5. IDEA FOR NEW MARKET {best_market}")
return best_market
if not filtered_markets:
logger.warning("No markets passed filtering — skipping")
return None

except Exception as e:
print(f"Error {e} \n \n Retrying")
self.one_best_market()
best_market = self.agent.source_best_market_to_create(filtered_markets)
logger.info("5. IDEA FOR NEW MARKET %s", best_market)
return best_market

def maintain_positions(self):
pass
Expand Down
21 changes: 12 additions & 9 deletions agents/application/cron.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
from agents.application.trade import Trader

import logging
import time

from scheduler import Scheduler
from scheduler import Scheduler as TimeScheduler
from scheduler.trigger import Monday

from agents.application.trade import Trader

logger = logging.getLogger(__name__)


class Scheduler:
class TradingScheduler:
def __init__(self) -> None:
self.trader = Trader()
self.schedule = Scheduler()
self.schedule = TimeScheduler()

def start(self) -> None:
logger.info("Starting trading scheduler loop")
while True:
self.schedule.exec_jobs()
time.sleep(1)


class TradingAgent(Scheduler):
class TradingAgent(TradingScheduler):
def __init__(self) -> None:
super()
self.trader = Trader()
self.weekly(Monday(), self.trader.one_best_trade)
super().__init__()
self.schedule.weekly(Monday(), self.trader.one_best_trade)
109 changes: 74 additions & 35 deletions agents/application/executor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import os
import json
import ast
import json
import logging
import math
import os
import re
from typing import List, Dict, Any

import math

from dotenv import load_dotenv
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
Expand All @@ -16,6 +16,8 @@
from agents.application.prompts import Prompter
from agents.polymarket.polymarket import Polymarket

logger = logging.getLogger(__name__)

def retain_keys(data, keys_to_retain):
if isinstance(data, dict):
return {
Expand All @@ -29,16 +31,38 @@ def retain_keys(data, keys_to_retain):
return data

class Executor:
def __init__(self, default_model='gpt-3.5-turbo-16k') -> None:
def __init__(
self,
default_model: str = None,
api_key: str = None,
base_url: str = None,
) -> None:
load_dotenv()
max_token_model = {'gpt-3.5-turbo-16k':15000, 'gpt-4-1106-preview':95000}
self.token_limit = max_token_model.get(default_model)
self.prompter = Prompter()
self.openai_api_key = os.getenv("OPENAI_API_KEY")
self.llm = ChatOpenAI(
model=default_model, #gpt-3.5-turbo"
temperature=0,
)

# Support any OpenAI-compatible provider via env vars or args
self.api_key = api_key or os.getenv("OPENAI_API_KEY") or os.getenv("LLM_API_KEY")
self.base_url = base_url or os.getenv("LLM_BASE_URL")
self.model = default_model or os.getenv("LLM_MODEL", "gpt-3.5-turbo-16k")

llm_kwargs = {
"model": self.model,
"temperature": 0,
"api_key": self.api_key,
}
if self.base_url:
llm_kwargs["base_url"] = self.base_url

self.llm = ChatOpenAI(**llm_kwargs)

max_token_model = {
"gpt-3.5-turbo-16k": 15000,
"gpt-4-1106-preview": 95000,
"gpt-4o-mini": 125000,
"gpt-4o": 125000,
}
self.token_limit = max_token_model.get(self.model, 15000)

self.gamma = Gamma()
self.chroma = Chroma()
self.polymarket = Polymarket()
Expand Down Expand Up @@ -98,7 +122,7 @@ def get_polymarket_llm(self, user_input: str) -> str:
else:
# If exceeding limit, process in chunks
chunk_size = len(combined_data) // ((total_tokens // token_limit) + 1)
print(f'total tokens {total_tokens} exceeding llm capacity, now will split and answer')
logger.info('total tokens %d exceeding llm capacity, now will split and answer', total_tokens)
group_size = (total_tokens // token_limit) + 1 # 3 is safe factor
keys_no_meaning = ['image','pagerDutyNotificationEnabled','resolvedBy','endDate','clobTokenIds','negRiskMarketID','conditionId','updatedAt','startDate']
useful_keys = ['id','questionID','description','liquidity','clobTokenIds','outcomes','outcomePrices','volume','startDate','endDate','question','questionID','events']
Expand Down Expand Up @@ -129,9 +153,7 @@ def filter_events(self, events: "list[SimpleEvent]") -> str:

def filter_events_with_rag(self, events: "list[SimpleEvent]") -> str:
prompt = self.prompter.filter_events()
print()
print("... prompting ... ", prompt)
print()
logger.info("... prompting ... %s", prompt)
return self.chroma.events(events, prompt)

def map_filtered_events_to_markets(
Expand All @@ -149,9 +171,7 @@ def map_filtered_events_to_markets(

def filter_markets(self, markets) -> "list[tuple]":
prompt = self.prompter.filter_markets()
print()
print("... prompting ... ", prompt)
print()
logger.info("... prompting ... %s", prompt)
return self.chroma.markets(markets, prompt)

def source_best_trade(self, market_object) -> str:
Expand All @@ -163,36 +183,55 @@ def source_best_trade(self, market_object) -> str:
description = market_document["page_content"]

prompt = self.prompter.superforecaster(question, description, outcomes)
print()
print("... prompting ... ", prompt)
print()
logger.info("... prompting superforecaster: %s", prompt)
result = self.llm.invoke(prompt)
content = result.content
logger.info("Superforecaster result: %s", content)

print("result: ", content)
print()
prompt = self.prompter.one_best_trade(content, outcomes, outcome_prices)
print("... prompting ... ", prompt)
print()
logger.info("... prompting trade: %s", prompt)
result = self.llm.invoke(prompt)
content = result.content

print("result: ", content)
print()
logger.info("Trade result: %s", content)
return content

def format_trade_prompt_for_execution(self, best_trade: str) -> float:
"""Parse LLM trade output into a safe USDC amount.

Expected format: 'price:0.5, size:0.1, side:BUY,'
Returns: size_fraction * usdc_balance
"""
data = best_trade.split(",")
# price = re.findall("\d+\.\d+", data[0])[0]
size = re.findall("\d+\.\d+", data[1])[0]
if len(data) < 2:
raise ValueError(
f"Trade output has unexpected format (need >=2 comma-separated parts): {best_trade!r}"
)

size_matches = re.findall(r"\d+\.?\d*", data[1])
if not size_matches:
raise ValueError(
f"Could not extract size from trade output: {data[1]!r}"
)

size = float(size_matches[0])
if not (0 < size <= 1):
raise ValueError(
f"Trade size {size} out of safe range (0, 1] — refusing to execute"
)

usdc_balance = self.polymarket.get_usdc_balance()
return float(size) * usdc_balance
amount = size * usdc_balance
logger.info(
"Trade size fraction: %.4f, USDC balance: %.2f, order amount: %.2f",
size,
usdc_balance,
amount,
)
return amount

def source_best_market_to_create(self, filtered_markets) -> str:
prompt = self.prompter.create_new_market(filtered_markets)
print()
print("... prompting ... ", prompt)
print()
logger.info("... prompting market creation: %s", prompt)
result = self.llm.invoke(prompt)
content = result.content
return content
Loading