Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - fruitlollipop/poly-maker: An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters · GitHub
Skip to content

Repository files navigation

poly-maker

A maker-only market-making bot for Polymarket CLOB V2, focused on political markets. Single async process, local-file config, typed and tested.

Warning

Market making on Polymarket is competitive and can lose money. This is a reference implementation and a research harness, not a guaranteed-profitable product. Test in --paper mode first; go live with small size.

What it does

  • Discovers political markets via the Gamma API (seconds) and ranks them by reward + rebate income vs. volatility/spread risk.
  • Maintains a live order book per token from the market WebSocket.
  • Quotes maker-only — every order is post-only. Fair-value + inventory-skew strategy that posts BUY-YES and BUY-NO as a two-sided quote, with live volatility/toxicity estimation and a regime machine that pulls quotes during news events (see Strategy).
  • Reconciles a target quote set against live orders with churn tolerances; runs the exchange heartbeat dead-man switch; enforces risk caps and a daily-loss kill switch.
  • Config, market selection, and state are local files + SQLite. An operator with the repo, a .env, and a funded wallet is a complete deployment.

Install

Uses uv and Python 3.12+.

uv sync --extra dev # install deps + dev tools
uv run polymaker --help

Configure

cp .env.example .env # then edit two values:
  • PK — the private key of your signer wallet
  • BROWSER_ADDRESS — your Polymarket address (shown on the profile / developer page)

Everything else is TOML under config/:

  • config.toml — wallet/engine/risk/execution settings
  • strategy.toml — named parameter profiles (political-longdated, political-hot)
  • markets.toml — the trade list (populated via the CLI below)

Use

# 1. discover + rank political markets (writes to state.db)
uv run polymaker scan
uv run polymaker markets
# 2. add markets to the trade list
uv run polymaker markets-add <slug> --profile political-longdated
# 3. dry run: full pipeline against the live feed, no orders posted
uv run polymaker run --paper
# 4. preflight the wallet before going live
uv run polymaker doctor
# 5. self-tests: a deep post-only order (free), then a real fill round-trip (~cents)
uv run polymaker livetest # place a deep post-only order + cancel (no fill)
uv run polymaker moneydoctor # limit rest + market buy + market sell, auto-flattens# 6. go live
uv run polymaker run
# ops
uv run polymaker status # positions / open orders
uv run polymaker cancel-all # panic button

Architecture

market WS ─▶ OrderBook ─▶ (wake) ─▶ Quoter ─▶ strategy (pure) ─▶ reconcile ─▶ ExecutionGateway
user WS ─▶ StateStore RiskManager ┘ (post-only, heartbeat)
Gamma ─▶ Catalog/scanner ─▶ SQLite periodic REST reconcile ┘

One async event loop. The strategy layer is a pure function (book, inventory, params, clock) → TargetQuotes — deterministic and unit-tested. The engine owns all I/O and state around it; the ExecutionGateway wraps py-clob-client-v2 (which handles the V2 EIP-712 signing) and offloads its blocking calls to a thread pool so the hot path never stalls. State (positions, orders, PnL, catalog) lives in one SQLite file; raw WS/order events are journaled to journal/ for replay.

Strategy

Maker-only, quoting both sides of each market as USDC-collateralized bids:

  • Fair value — depth-weighted microprice off the live book, nudged by an EWMA of signed trade flow.
  • Quote construction — reservation price r = FV − skew(inventory); half-spread δ = base + c_vol·σ + c_tox·toxicity. Post BUY-YES at r − δ and BUY-NO at (1 − r) − δ. Because both legs are bids that sum below 1, a filled pair merges back to USDC at locked edge 1 − p − q — a maker-only exit that never crosses the spread.
  • Inventory skew — net position leans both quotes: long YES → bid YES lower, bid NO higher (acquire the offsetting leg). Size tapers as inventory approaches a soft cap, then the adding side is pulled entirely.
  • Volatility / toxicity — realized-vol and per-fill markout (adverse selection) EWMAs widen the spread and shrink size in markets that pick us off.
  • Regime machine — per market: QUIET (farm rewards in-band), TRENDING (lean + widen + half size), EVENT (sweep/jump detected → pull quotes, cool off), REDUCE_ONLY (inventory cap / near end date → exits only), HALTED (stale data / resolved / kill switch → cancel all).
  • Rewards + rebates — quotes stay inside the liquidity-rewards band in QUIET; the market selector also scores the new maker-rebate program (a share of taker fees rebated to makers).
  • Risk — per-market notional cap, neg-risk event-group worst-case cap, total exposure cap, daily-loss kill switch, WS-staleness halt.

Tune it all via profiles in config/strategy.toml.

Develop

uv run pytest # unit suite (offline)
POLYMAKER_LIVE=1 uv run pytest tests/test_live_marketdata.py # live WS integration
uv run ruff check src tests # lint
uv run mypy src # types (strict)

Status

Implemented and live-verified end to end (auth → book → strategy → sign → post → cancel): config, catalog/scanner, order book + analytics, strategy (FV, vol/toxicity, regime, quoting), state store + lifecycle, execution gateway + reconciler + heartbeat, market/user websockets, risk manager, merger (EOA path), engine, CLI, paper mode, journal capture. 83 tests; ruff + mypy strict clean.

Not yet built: a replay backtester over the captured journals, and external data feeds (polls / news / cross-venue). Merging through a Safe/proxy wallet routes a tx via the relayer and isn't wired yet — until then inventory exits via limit sells rather than merging.

License

MIT

About

An automated market making bot for the Polymarket Prediction Market that provides liquidity by maintaining orders on both sides of the order book with customizable parameters

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages