CLAUDE.md · git:20260906.08ff3f1 · 2026-09-06 · sha256 49269ad2fa1db4da
CLAUDE.md git:20260906.08ff3f1A
Immutable. This exact content is served forever at /api/v1/blob/49269ad2fa1db4da.
# Polymarket Trading Application
## Project Overview
A multi-venue prediction-market inefficiency engine with Python/FastAPI backend and React/TypeScript frontend. The application scans real-API exchanges (Polymarket and Kalshi), detects pricing inefficiencies, and executes through a unified paper and live trading path. Time-to-resolution is a first-class ranking axis. All trading defaults to paper mode; live mode requires explicit confirmation and structural fences.
Trader mimicry (copy-trading) was removed deliberately: per-wallet samples on prediction markets are too small and too correlated to separate skill from variance.
## Tech Stack
### Backend (Python 3.11+)
- **Framework**: FastAPI with async support
- **ORM**: SQLAlchemy 2.0 with async sessions
- **Database**: PostgreSQL + TimescaleDB extension for time-series
- **Cache/Queue**: Redis + Celery for background tasks
- **Blockchain**: py-clob-client, web3.py for Polygon
- **Testing**: pytest, pytest-asyncio, factory_boy
### Frontend (React 19+)
- **Language**: TypeScript 5.9+
- **Framework**: React 19 with Vite
- **State**: TanStack Query (React Query) + Zustand
- **UI**: Tailwind CSS 4 (no shadcn/ui — there is no `components.json` or `components/ui/` in this repo)
- **Charts**: Recharts for data visualization
- **HTTP**: axios for API calls
- **Build**: Vite for development and production
## Project Structure
```
polymarket-trader/
├── backend/
│ ├── app/
│ │ ├── __init__.py
│ │ ├── main.py # FastAPI app entry
│ │ ├── config.py # Settings and env vars
│ │ ├── database.py # DB connection and sessions
│ │ ├── models/ # SQLAlchemy models
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── market.py
│ │ │ ├── trade.py
│ │ │ ├── position.py
│ │ │ ├── strategy.py
│ │ │ ├── backtest.py
│ │ │ ├── event_link.py # Cross-venue event matching
│ │ │ ├── book_snapshot.py # Recorded order-book snapshots
│ │ │ └── intent.py # Multi-leg execution intents
│ │ ├── api/ # API routes
│ │ │ ├── __init__.py
│ │ │ ├── deps.py # Dependencies
│ │ │ └── routes/
│ │ │ ├── backtesting.py
│ │ │ ├── arbitrage.py
│ │ │ ├── trading.py
│ │ │ └── links.py # Event link API
│ │ ├── venues/ # Venue adapters (multi-venue)
│ │ │ ├── __init__.py
│ │ │ ├── base.py # VenueAdapter protocol
│ │ │ ├── types.py # Normalized types
│ │ │ ├── fees.py # Fee models
│ │ │ ├── registry.py # Venue registry
│ │ │ ├── polymarket/ # Polymarket adapter
│ │ │ └── kalshi/ # Kalshi adapter
│ │ ├── execution/ # Order routing and fills
│ │ │ ├── router.py # OrderRouter (single entry point)
│ │ │ ├── ledger.py # Position ledger
│ │ │ ├── fill_engine.py # SimulatedFillEngine
│ │ │ ├── fences.py # Live-trading safety checks
│ │ │ └── reconcile.py # Order reconciliation
│ │ ├── services/ # Business logic
│ │ │ ├── backtesting/ # Backtest engine
│ │ │ │ ├── __init__.py
│ │ │ │ ├── engine.py
│ │ │ │ ├── metrics.py
│ │ │ │ ├── data_replay.py
│ │ │ │ └── sweep.py # Capital-level decay analysis
│ │ │ ├── matching/ # Event matching service
│ │ │ ├── scoring.py # Opportunity scoring
│ │ │ ├── scanner.py # Market scanning
│ │ │ └── data_collector.py # Public trade tape collection
│ │ ├── strategies/ # Trading strategies
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── cross_venue_arbitrage.py
│ │ │ ├── binary_complement_arbitrage.py
│ │ │ ├── multi_outcome_bundle_arbitrage.py
│ │ │ └── ...other strategies
│ │ ├── scripts/ # CLI tools
│ │ │ └── sweep.py # Run capital sweep on strategies
│ │ ├── tasks/ # Background tasks
│ │ │ ├── __init__.py
│ │ │ ├── execution.py
│ │ │ ├── scanner.py
│ │ │ └── backtesting.py
│ │ └── utils/ # Utilities
│ │ ├── time.py # TZ-aware datetime helpers
│ │ └── ...
│ ├── tests/ # Test suite
│ │ ├── conftest.py
│ │ ├── fixtures/ # Recorded venue payloads
│ │ ├── venues/ # Adapter contract tests
│ │ ├── test_fences.py # Live-order isolation check
│ │ └── ...
│ ├── alembic/ # DB migrations
│ │ ├── versions/
│ │ ├── env.py
│ │ └── alembic.ini
│ ├── requirements.txt
│ └── pyproject.toml
├── frontend/
│ ├── src/
│ │ ├── main.tsx
│ │ ├── App.tsx
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── stores/
│ │ ├── types/
│ │ └── utils/
│ ├── package.json
│ ├── vite.config.ts
│ └── tsconfig.json
├── .env.example # Environment variable template
├── CLAUDE.md # This file
└── README.md
```
## Commands
### Backend — Development and Testing
```bash
cd backend
# Run all tests (SQLite, no network)
python3 -m pytest -q
# Run tests with coverage
python3 -m pytest --cov=app --cov-report=html --cov-report=term
# Type checking
mypy app/services/backtesting app/strategies/base.py
# Linting
ruff check app/
# Database migrations — offline (no DB needed)
alembic upgrade head --sql
# Capital-level sweep on synthetic data
python3 -m app.scripts.sweep --synthetic --levels 500,5000,50000 --out results.json
# Development server
uvicorn app.main:app --reload --port 8000
```
### Frontend
```bash
cd frontend
# Install dependencies
npm ci --no-audit --no-fund
# Development server
npm run dev
# Build for production
npm run build
# Type check
npx tsc -p tsconfig.app.json --noEmit
# Linting
npm run lint
```
## Code Style
### Python
- Use type hints on ALL functions
- Async functions for I/O operations
- Follow PEP 8 with Black formatting
- Docstrings in Google style format
- Use Pydantic for all data validation
- Prefer composition over inheritance
- Use dependency injection via FastAPI Depends
Example:
```python
from typing import Optional
from pydantic import BaseModel
class MarketData(BaseModel):
"""Market data model with validation."""
condition_id: str
question: str
outcome_prices: dict[str, float]
volume_24h: float
async def get_market(
condition_id: str,
client: ClobClient = Depends(get_clob_client)
) -> MarketData:
"""Fetch market data from Polymarket."""
...
```
### TypeScript/React
- Strict TypeScript with no `any` types
- Functional components with hooks
- Use TanStack Query for all API calls
- Zustand for global state (minimal)
- Component files: PascalCase.tsx
- Utility files: camelCase.ts
- Types in separate `.types.ts` files
Example:
```typescript
interface MarketCardProps {
market: Market;
onSelect: (id: string) => void;
}
export function MarketCard({ market, onSelect }: MarketCardProps) {
const { data, isLoading } = useMarketData(market.id);
// ...
}
```
## API Design
### REST Endpoints Pattern
```
GET /api/v1/markets # List markets
GET /api/v1/markets/{id} # Get market details
GET /api/v1/markets/{id}/orderbook # Get orderbook
POST /api/v1/trading/orders # Place order
DELETE /api/v1/trading/orders/{id} # Cancel order
GET /api/v1/trading/positions # Get positions
POST /api/v1/arbitrage/scan # Scan for opportunities
GET /api/v1/backtests # List backtests
POST /api/v1/backtests # Run backtest
GET /api/v1/bots # List bots
POST /api/v1/bots/{id}/start # Start bot
```
### WebSocket Events
Not implemented. `socket.io-client` is listed in `frontend/package.json` but
nothing in this repo emits or subscribes to it, and no backend WebSocket
route exists. The Kalshi adapter's `NotImplementedError` for its own
WebSocket is documented separately in `.claude/skills/kalshi-api/SKILL.md`.
Treat any future WebSocket event contract as new work, not as something
already agreed upon.
## Polymarket Integration
### Key APIs
1. **CLOB API** (`https://clob.polymarket.com`)
- Trading, orders, positions
- Requires API key authentication
2. **Gamma API** (`https://gamma-api.polymarket.com`)
- Market metadata, events
- Public, no auth required
### Authentication
```python
from py_clob_client.client import ClobClient
client = ClobClient(
host="https://clob.polymarket.com",
key=PRIVATE_KEY,
chain_id=137, # Polygon
signature_type=1, # For email/Magic wallet
funder=FUNDER_ADDRESS
)
client.set_api_creds(client.create_or_derive_api_creds())
```
## Environment Variables
### Application
```bash
TRADING_MODE=paper # "paper" or "live" (defaults to paper)
LIVE_TRADING_CONFIRMATION= # Must be "I_UNDERSTAND_REAL_MONEY" to enable live mode
SECRET_KEY=your_secret_key
DEBUG=true
LOG_LEVEL=INFO
```
### Polymarket
```bash
POLYMARKET_PRIVATE_KEY= # Private key (never commit)
POLYMARKET_FUNDER_ADDRESS= # Wallet address
POLYMARKET_API_KEY=
POLYMARKET_API_SECRET=
POLYMARKET_API_PASSPHRASE=
```
### Kalshi (Trade API v2)
```bash
KALSHI_API_KEY_ID= # Public identifier
KALSHI_PRIVATE_KEY_PEM= # RSA private key (never commit)
KALSHI_ENV=demo # "demo" or "prod" (defaults to demo)
# KALSHI_BASE_URL= # Override base URL if needed
KALSHI_TAKER_FEE_RATE=0.07 # Standard taker rate
KALSHI_MAKER_FEE_RATE=0.0
```
### Costs and Risk Management
```bash
POLYMARKET_TAKER_FEE_OVERRIDES={} # Per-category fee overrides (JSON)
REDEMPTION_GAS_USD=0.05 # Polygon redemption gas cost
TRANSFER_LATENCY_HOURS=72 # ACH/wire settlement between venues
TRANSFER_COST_USD=5.0 # USD cost per venue transfer
LIQUIDITY_FRACTION=0.02 # Synthetic book depth multiplier
NEAR_RESOLUTION_HOURS=72 # Hours-to-close for near-resolution bucket
SETTLEMENT_DELAY_HOURS=24 # Hours post-resolution until payout
MIN_HOURS_FOR_ANNUALIZATION=6
MIN_VIABLE_ANNUALIZED=0.05
MIN_TRADE_USD=10.0 # Minimum notional to attempt
```
### Live Trading Fences
```bash
KILL_SWITCH_PATH=TRADING_KILL_SWITCH # File existence halts all orders
MAX_ORDER_NOTIONAL_USD=250
MAX_DAILY_LOSS_USD=100
MAX_OPEN_NOTIONAL_USD=1000
MAX_NEAR_RESOLUTION_NOTIONAL_USD=500
```
### Order Routing
```bash
PAPER_STARTING_BALANCES={"polymarket": 1000.0, "kalshi": 1000.0}
RECONCILE_GRACE_S=120
ALL_OR_NONE_FILL_TOLERANCE=0.995
UNWIND_SLIPPAGE_TICKS=5
```
### Database & Frontend
```bash
DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/polymarket
REDIS_URL=redis://localhost:6379
VITE_API_URL=http://localhost:8000/api/v1
```
## Testing Requirements
- All new features must have tests
- Backend: pytest with >80% coverage
- Frontend: no test runner is configured today (`frontend/package.json` has
no `test` script and does not depend on `vitest` or
`@testing-library/react`); do not invent a test command that cannot run
- Integration tests for trading flows
- Mock Polymarket API in tests
## Security Considerations
- NEVER commit private keys or API secrets
- Use environment variables for all secrets
- Implement rate limiting on all endpoints
- Validate all user inputs
- Use prepared statements (SQLAlchemy handles this)
- Implement proper CORS policies
- Sign all orders client-side
## Performance Guidelines
- Use async/await for all I/O
- Implement connection pooling for DB
- Cache market data in Redis (TTL: 5s)
- Use WebSocket for real-time updates
- Batch database writes where possible
- Use TimescaleDB hypertables for time-series
## Money Invariants
These are structural safety rules enforced by code and tests. Operators and developers must understand them.
- "`TRADING_MODE` defaults to `paper`; live requires `LIVE_TRADING_CONFIRMATION=I_UNDERSTAND_REAL_MONEY` and no `TRADING_KILL_SWITCH` file"
- "Only `app/venues/*/live.py` (Polymarket, Kalshi) and `app/services/polymarket/client.py` (the sanctioned wrapper declaring the `py_clob_client` calls, callable only from `venues/polymarket/live.py`) may contain venue order placement/cancellation — `tests/test_fences.py` enforces this by AST walk (see `LIVE_MODULES`/`WRAPPER_MODULE` there)"
- "Never place an order from a test, a verifier, or CI"
- "Run exactly ONE order-routing process. The near-resolution bucket cap and the position ledger are serialized by an `asyncio.Lock` scoped to one event loop in one process (`app/execution/router.py`). A second API worker or Celery worker sharing the database can breach the cap and lose filled positions to a concurrent-update overwrite. The portable fix (optimistic concurrency on `positions`) is not implemented."
## Git Workflow
- Branch naming: `feature/`, `fix/`, `refactor/`
- Commit messages: Conventional commits
- PR required for main branch
- Run tests before committing