A small Python backtesting project for experimenting with simple Polymarket trading strategies against historical price data.
The project fetches market history from Polymarket, asks the user to select a market outcome, runs a strategy over the returned price series, and reports realized/unrealized PnL, equity, orders, trades, and ranked trade results.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpython main.pyThe program prompts for:
Slug: a Polymarket market or event slug.Interval: one of1h,6h,1d,1w,1m,all, ormax.Outcome: the outcome token to backtest.Initial capital: starting cash for the simulation.
main.py
-> data_setup.get_data()
-> backtester.backtester(data)
-> strategy.strategy(...)
-> models.Order
-> models.Position
The application entry point. It loads historical market data, passes it into the backtester, and prints the resulting performance dictionary.
Handles user input and Polymarket data retrieval.
The data flow is:
- Look up the provided slug using the Gamma markets API.
- If no market is found, treat the slug as an event slug and use the first market from that event.
- Fetch CLOB market metadata using the market condition ID.
- Ask the user which outcome token to test.
- Fetch historical price data from the CLOB
prices-historyendpoint. - Return the history as a pandas
DataFrame.
Contains the strategy function used by the backtester.
Current strategy contract:
def strategy(time, price, capital, long_position, short_position):
return [Order(price, quantity, direction)]The strategy receives the current timestamp, current YES price, available capital, and remaining long/short position sizes. It returns a list of Order objects.
Order behavior:
direction="LONG"buys or sells the selected YES outcome.direction="SHORT"buys or sells the inverse side, priced as1 - yes_price.- Positive
quantityopens or increases a position. - Negative
quantitycloses an existing position.
The included sample strategy buys 50 LONG shares when the selected outcome price is below 0.4.
Controls the simulation loop, matching model, cash accounting, and position tracking.
For each historical price row, it:
- Calls
strategy(...). - Validates each order direction and quantity.
- Converts LONG/SHORT directions into the correct simulated fill price.
- Opens positions for positive quantities when enough cash is available.
- Closes positions for negative quantities when enough position size is available.
- Tracks orders, trades, realized PnL, unrealized PnL, final equity, cash, and remaining positions.
The matching model is intentionally simple: orders are filled immediately at the historical price for LONG positions, or 1 - price for SHORT positions. It does not simulate queue priority, spread, slippage, partial fills from market depth, fees, or liquidity constraints.
Defines the core domain objects:
Order: a requested trade with price, quantity, and direction.Position: average-entry position accounting with add, close, market value, realized PnL, and unrealized PnL helpers.
backtester(...) returns a dictionary containing:
realized_pnlunrealized_pnlfinal_equitytotal_pnlorderstradesranked_tradescashremaining_long_positionremaining_short_positionlong_average_entry_priceshort_average_entry_price
This backtester uses Polymarket historical price data, not historical order book snapshots.
Polymarket does not provide historical order book access through the endpoints used by this project. As a result, the backtester cannot reconstruct bid/ask depth, available liquidity, queue position, historical spreads, or whether a real market order of a given size would have filled at the simulated price.
Because of this, results should be treated as approximate strategy research, not execution-accurate performance.
Other current limitations:
- No trading fees.
- No slippage model.
- No liquidity or market-depth checks.
- No partial fills.
- No explicit resolution/payout handling.
- Event slugs default to the first market in the event response.
To test a new strategy, edit strategy.py and return one or more Order objects from strategy(...).
For example:
from models import Order
def strategy(time, price, capital, long_position, short_position):
orders = []
if price < 0.35 and capital >= price * 25:
orders.append(Order(price, 25, "LONG"))
if price > 0.65 and long_position >= 25:
orders.append(Order(price, -25, "LONG"))
return orders