PQAP Architecture
Overview
PQAP (Polymarket Quant Automation Platform) is a trading system for prediction markets. It's designed around finding real edge - not just fitting historical noise, but discovering genuine market inefficiencies.
Core Philosophy
- Data First: Collect extensive historical data before betting
- Validate Everything: Use calibration analysis to verify predictions
- Paper Trade: Always paper trade before risking real capital
- Measure Edge: Track expected value, not just win rate
System Components
┌─────────────────────────────────────────────────────────────────┐
│ PQAP Main │
│ (src/main.py - PQAP class) │
├──────────────┬──────────────┬──────────────┬───────────────────┤
│ Ingest │ Strategies │ Execution │ Analytics │
│ │ │ │ │
│ - API Client │ - Registry │ - Paper │ - Metrics │
│ - Orderbooks │ - Signals │ - Live │ - Calibration │
│ - Markets │ - Risk │ - Tracking │ - Backtesting │
└──────────────┴──────────────┴──────────────┴───────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ Data Layer │
│ - SQLite (market_history.db) - Historical snapshots │
│ - SQLite (pqap_dev.db) - Trades, signals, strategy state │
└─────────────────────────────────────────────────────────────────┘
Directory Structure
polymarket/
├── src/
│ ├── main.py # Application entry point
│ ├── core/ # Core infrastructure
│ │ ├── config.py # Configuration loading
│ │ └── events.py # Event bus for decoupled communication
│ │
│ ├── ingest/ # Data ingestion
│ │ ├── polymarket.py # Polymarket API client
│ │ └── orderbook.py # Orderbook management
│ │
│ ├── strategies/ # Trading strategies
│ │ ├── base.py # Base strategy class, Signal dataclass
│ │ ├── registry.py # Strategy discovery and management
│ │ ├── arb_scanner.py # Arbitrage opportunity scanner
│ │ ├── dual_arb/ # Dual arbitrage strategy
│ │ ├── momentum/ # Momentum strategy
│ │ └── value_bet/ # Value betting strategy
│ │
│ ├── execution/ # Order execution
│ │ ├── engine.py # Live execution engine
│ │ └── paper_trading.py # Paper trading simulation
│ │
│ ├── risk/ # Risk management
│ │ └── engine.py # Risk checks and kill switch
│ │
│ ├── tracking/ # P&L and attribution
│ │ ├── attribution.py # Strategy attribution
│ │ └── storage.py # Database storage
│ │
│ ├── analytics/ # Performance analytics
│ │ ├── metrics.py # Strategy metrics, Sharpe, Kelly, etc.
│ │ └── resolution_tracker.py # Outcome tracking
│ │
│ ├── backtest/ # Backtesting framework
│ │ ├── engine.py # Backtest execution
│ │ ├── simulator.py # Market simulation
│ │ └── cli.py # Command-line interface
│ │
│ ├── data/ # Data collection
│ │ └── collector.py # Historical data capture
│ │
│ ├── admin/ # Web admin interface
│ │ ├── api.py # REST API and web UI
│ │ └── controller.py # Strategy control
│ │
│ └── alerts/ # Alerting
│ └── telegram.py # Telegram notifications
│
├── configs/ # Configuration files
│ ├── dev.yaml # Development config
│ └── prod.yaml # Production config
│
├── data/ # Data storage
│ ├── market_history.db # Historical market snapshots
│ └── pqap_dev.db # Trading state
│
└── deploy/ # Deployment
├── Dockerfile
└── docker-compose.yaml
Data Flow
1. Market Data Ingestion
Polymarket API → PolymarketClient → Markets cache → Strategies
↓
DataCollector → market_history.db
2. Signal Generation
Market Update → Strategy.on_orderbook_update() → Signal
↓
Risk Check
↓
┌───────────────┴───────────────┐
▼ ▼
Paper Trade Live Trade
↓ ↓
PaperTradingEngine ExecutionEngine
3. Performance Tracking
Trades → AttributionTracker → MetricsStorage → PerformanceReport
↑
ResolutionTracker ────┘
(market outcomes)
Key Classes
Signal (src/strategies/base.py)
The core data structure for trading decisions:
@dataclass
class Signal:
strategy_id: str
market_id: str
signal_type: SignalType # BUY, SELL, HOLD
confidence: float # 0.0 to 1.0
size_suggestion: Decimal # Fraction of capital
# ... other fields
Strategy (src/strategies/base.py)
Abstract base for all strategies:
class Strategy(ABC):
@abstractmethod
def on_orderbook_update(self, market: Market) -> Signal | None:
"""Process market update, optionally return signal."""
pass
@abstractmethod
def on_trade_executed(self, trade: dict) -> None:
"""Handle trade execution notification."""
pass
BacktestEngine (src/backtest/engine.py)
Runs strategies against historical data:
engine = BacktestEngine("data/market_history.db")
result = engine.run(
strategy=my_strategy_function,
start_date=datetime(2024, 1, 1),
end_date=datetime(2024, 6, 1)
)
print(result.summary())
Finding Real Edge
The key insight: most trading strategies fail because they fit noise, not signal.
The Edge Validation Process
- Collect Data
- Run the system to accumulate market snapshots
-
More data = more reliable backtests
-
Backtest Strategies
bash python -m src.backtest.cli momentum --verbose -
Check Calibration
- Are 70% confidence predictions winning ~70% of the time?
-
Use CalibrationAnalyzer to verify
-
Measure Expected Value
- Positive EV = edge
-
Negative EV = no edge, don't trade
-
Paper Trade
- Validate in real-time without risking capital
-
Watch for execution slippage
-
Scale Carefully
- Use Kelly criterion for position sizing
- Start with Kelly/4 or Kelly/2
Key Metrics
| Metric | Good Value | Meaning |
|---|---|---|
| Expected Value | > $0 | Average profit per trade |
| Win Rate | > 50% | Percentage of winning trades |
| Profit Factor | > 1.5 | Gross profit / gross loss |
| Sharpe Ratio | > 1.0 | Risk-adjusted returns |
| Kelly Fraction | > 0 | Optimal bet size (edge exists) |
| Brier Score | < 0.20 | Prediction accuracy |
| ECE | < 0.10 | Calibration error |
Configuration
configs/dev.yaml
env: dev
initial_capital: 100 # Small for testing
enabled_strategies:
- dual_arb_v1
- momentum_v1
- value_bet_v1
risk_limits:
max_capital_per_market: 0.15
daily_loss_limit: 0.15
max_open_positions: 20
strategies:
momentum_v1:
parameters:
lookback_periods: 6
min_momentum: 0.02
Admin Interface
Access at http://localhost:8080
| Page | URL | Purpose |
|---|---|---|
| Dashboard | / | Strategy overview and controls |
| Markets | /markets | Market prices and arb opportunities |
| Signals | /signals | Signal generation history |
| Paper Trading | /paper | Simulated positions and P&L |
| P&L Report | /pnl | Performance attribution |
| Backtest | /backtest | Historical data status |
API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /health | GET | Health check |
| /api/status | GET | System status |
| /api/strategies | GET | List all strategies |
| /api/strategies/{id}/enable | POST | Enable strategy |
| /api/strategies/{id}/disable | POST | Disable strategy |
| /api/signals | GET | Recent signals |
| /api/trades | GET | Recent trades |
| /api/pnl | GET | P&L data |
| /api/backtest/stats | GET | Historical data stats |
Extending the System
Adding a New Strategy
- Create directory:
src/strategies/my_strategy/ - Create
__init__.pywith strategy class - Extend
Strategybase class - Implement
on_orderbook_update()andon_trade_executed() - Add to registry in
src/strategies/registry.py - Enable in config:
enabled_strategies: [my_strategy_v1]
Adding New Data Sources
- Add collector method to
src/data/collector.py - Create corresponding table in database schema
- Call from data collection loop in
src/main.py
Database Schema
market_history.db
market_snapshots- Point-in-time market pricesmarket_resolutions- Market outcome resolutionsorderbook_snapshots- Orderbook depth dataprice_history- Price timeseries from API
pqap_dev.db
signals- Generated trading signalstrades- Executed tradesstrategy_state- Strategy configurationcollection_stats- Data collection metadata
Known Limitations
- No True Arbitrage: YES + NO always = 1.00 on Polymarket (no simple arb)
- API Rate Limits: Live trading limited by Polymarket API
- Resolution Delay: Market outcomes take time to confirm
- Liquidity: Small markets may have high slippage
Future Directions
- More Data Sources: External news, sentiment, related markets
- ML Models: Trained on historical resolution data
- Multi-Market Correlations: Find cross-market inefficiencies
- Automated Rebalancing: Dynamic position management