Skip to content

Bachamht/YieldPilot

Repository files navigation

YieldPilot

A Q-Score liquidity-reward farming bot for Polymarket

Persistent, two-sided quoting that earns Polymarket's daily liquidity rewards.

Python Platform Chain Status License


⚠️ DISCLAIMER

YieldPilot trades real money on a live prediction market with your private key. It is provided as-is, with no warranty, for research and educational purposes only. Nothing in this repository is financial advice. You are solely responsible for any losses, missed payouts, smart-contract risk, regulatory exposure, or compliance obligations in your jurisdiction. Do not run this bot with capital you cannot afford to lose. Read the code before you run it.


Table of Contents


What It Does

YieldPilot earns Polymarket's daily Q-Score liquidity rewards by maintaining persistent, two-sided quotes around the mid-price on selected binary markets. The bot continuously sizes orders to meet rewardsMinSize on both BUY and SELL, places them at a configurable distance from mid, and refreshes them only when necessary — preserving the time-on-book that the Q-Score formula values.

The revenue model is straightforward: daily reward payout from Polymarket > occasional adverse fills. The bot is engineered for that bottom line.

How Q-Score Rewards Work

Polymarket's liquidity reward formula values three things:

  1. Two-sided presence — both BUY and SELL ≥ rewardsMinSize shares simultaneously.
  2. Proximity to midQ = ((maxSpread − distance) / maxSpread)².
  3. Time-on-book — Q-score is integrated over time; cancels reset the clock.

YieldPilot quotes at maxSpread × 40% from mid by default. Tighter quoting scores higher per sample but fills more often (each fill is a small adverse-selection cost and resets time-on-book). Wider quoting reduces fills but lowers per-sample Q. The default sits in the middle and is fully configurable via FARM_SPREAD_FRACTION.

The optimization target is simply: daily reward income > daily adverse-fill losses.

Features

  • Reward-first quoting — wide passive two-sided quotes sized to exact rewardsMinSize, no inventory skew (stable inventory keeps both sides eligible).
  • Per-token state machineBUILD (acquire inventory via FOK taker on tight books, or passive GTC on wide books) → FARM (passive reward quoting). Automatic transition.
  • Reward-aware market scanner — filters out markets that don't pay rewards or aren't affordable; scores by net daily reward value (estimated reward income minus volatility-adjusted loss expectation).
  • Live Q-Score reward estimator — samples both YES and NO order books, computes Q_min per Polymarket's formula, predicts your daily payout share before committing capital.
  • Cross-book safety — multi-step price clamping prevents post-adjustment quotes from crossing the book or sitting unfillably behind the queue.
  • On-chain truth — every SELL is clamped to ctf.balanceOf(); data-api responses are treated as hints, not authoritative state.
  • WebSocket order book with REST fallback, stale-data watchdog, and exponential-backoff reconnect.
  • Risk controls — daily loss budget, volatility pause threshold, fill-rate auto-widen, kill switch, low-balance pair-merge recovery.
  • Reward income tracking — polls Polymarket's /rewards/user endpoint and surfaces daily payouts in the dashboard.
  • Telegram notifications (optional) — startup/shutdown summaries, hourly PnL reports, reward income.
  • Dry-run simulator for strategy iteration without live capital.

Architecture

main.py (MarketMaker main loop)
├── market_scanner.py   — Gamma API scan, reward-first scoring, Q-estimator
├── orderbook.py        — WS real-time book + REST fallback + volatility calc
├── quote_engine.py     — Wide passive two-sided quoting for Q-score
├── order_manager.py    — Order lifecycle, fill detection, liquidation
├── risk_manager.py     — Position state, PnL bucketing, kill switch
├── merger.py           — On-chain YES+NO pair merging (recovery only)
├── dashboard.py        — Terminal status panel
├── simulator.py        — DRY_RUN fill simulation
├── trade_logger.py     — SQLite trade DB
├── notifier.py         — Telegram alerts + scheduled reports
└── config.py           — Env-driven config (dataclass, .env loaded)

Tech stack: Python 3.12 (async/await), py-clob-client, Polymarket Gamma API, Polygon (chain_id 137, USDC.e), SQLite, websockets.

Quick Start

Prerequisites

  • Python 3.12+
  • A funded Polygon wallet (USDC.e + a small amount of POL for gas)
  • Polymarket account using that wallet
  • USDC + CTF approvals granted to the Polymarket exchange contracts (one-time, on-chain TX — see Polymarket's developer docs)
  • Telegram bot + chat ID (optional, for notifications)

Install

git clone https://github.com/Bachamht/YieldPilot.git
cd YieldPilot
python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Configure

cp .env.example .env
# Edit .env — set PRIVATE_KEY (your EOA private key, 0x-prefixed)
# and tune capital limits for your account size.

Test in dry-run mode first

DRY_RUN=true python main.py

Once the dashboard renders without errors and you see "Cold start complete", flip DRY_RUN=false to go live.

Configuration

All configuration is via environment variables (loaded from .env). The most important knobs:

Strategy mode

Variable Default Description
FARM_MODE true Pure Q-score farming. Leave this on.
FARM_SPREAD_FRACTION 0.40 Distance from mid as fraction of maxSpread. Wider = fewer fills.
FARM_MAX_BEHIND_TICKS 15 Soft cap: max ticks behind book edge after price adjustments.
FARM_DAILY_LOSS_BUDGET 30 USD adverse-fill loss budget before pausing.
FARM_VOL_PAUSE_THRESHOLD 0.02 Pause quoting when realized volatility exceeds this.
DRY_RUN false true = simulation, false = live trading.

Capital and risk

Variable Default Description
MAX_ACTIVE_MARKETS 23 Each rewardsMinSize=200 market needs ~$400 capital.
MAX_TOTAL_POSITION 8001500 Total position limit (USDC).
MAX_DAILY_LOSS 60 Daily loss limit; triggers kill switch.
MIN_OPERATING_BALANCE 50 Min USDC required to continue placing new positions.

Market selection

Variable Default Description
MAX_VOLATILITY 0.025 Reject high-volatility markets.
MIN_DAILY_VOLUME 5000 Min 24h volume in USDC.
PROB_MIN 0.10 Lower probability bound (strict).
PROB_MAX 0.90 Upper probability bound (strict).
MIN_DAYS_TO_EXPIRY 7 Min days to settlement for non-sports markets.

See config.py for the full list (~40 knobs).

Running the Bot

# Simulation — no real orders sent
DRY_RUN=true python main.py

# Live trading
DRY_RUN=false python main.py

Send SIGTERM (or Ctrl+C) to shut down gracefully. The bot will sync open fills, cancel orders, on-chain merge any YES+NO pairs to recover collateral, sell remaining single-sided positions, and redeem settled markets before exiting. Do not SIGKILL — graceful shutdown involves multiple on-chain transactions and may take several minutes; a force-kill mid-merge can strand inventory.

The bot also recognizes a .smart_shutdown sentinel file in the working directory: if present at SIGTERM time, the shutdown sequence preserves orders and positions in markets that are still reward-eligible (so a restart doesn't reset Q-Score time-on-book). Create the file before sending SIGTERM if you want this behavior.

How a Session Looks

  1. Startup — Sync positions from data-api, adopt any legacy positions as orphans, selectively cancel stale orders, connect WebSocket.
  2. Market scan — Periodically fetch markets from the Gamma API, filter for reward eligibility and affordability, score by net daily reward value, select top N. Hysteresis prevents incumbent eviction on marginal score drift.
  3. BUILD phase — For each new token, acquire inventory until my_shares ≥ rewardsMinSize. Tight-spread markets use a FOK taker; wide-spread markets build passively at best_bid.
  4. FARM phase — Place wide passive two-sided quotes at maxSpread × FARM_SPREAD_FRACTION from mid. Refresh only when price drifts past threshold. Both BUY and SELL stay on the book continuously.
  5. Risk monitoring — Volatility pause, daily loss budget, fill-rate auto-widen, low-balance pair-merge recovery, expiry-based liquidation.
  6. Reward tracking — Periodically poll Polymarket's /rewards/user endpoint and surface daily payouts.
  7. Shutdown — Cancel orders → on-chain merge YES+NO pairs → SELL remaining single-sided positions → redeem settled markets.

Project Status

Experimental. The codebase is functional and self-contained, but it has not been audited and is provided without support. It is hardened against many of the failure modes that arise when running market-making logic against a live CLOB (data-api lag, WebSocket teardown races, post_only crosses, phantom positions, sync clobbering, cost-basis writeoff races, BUILD over-buy on wide spreads, and others), but it is not hardened against:

  • Markets, instruments, or chains beyond Polymarket on Polygon.
  • Account configurations other than EOA-with-proxy.
  • Concurrent execution of multiple bot instances against the same wallet.
  • Adverse changes to Polymarket's reward formula, API contracts, or fee structure.

If you fork it, expect to put real work into adapting it to your situation.

Known Limitations

  • Single-account, single-chain — no multi-account orchestration, no cross-venue arbitrage.
  • Reward formula assumptions — the Q-Score estimator is calibrated against a small empirical sample. Polymarket can change the formula at any time, and the calibration constant may need re-tuning.
  • No backtester — the dry-run simulator covers fill detection and order lifecycle but does not replay historical books. Strategy iteration requires patient live experimentation.
  • No web UI — terminal dashboard + Telegram only.
  • Telegram-only notifications — no Discord / email / webhook integrations.

Contributing

Issues and pull requests are welcome, with a few notes:

  • This codebase is committed to the reward-farming strategy. PRs that reintroduce spread-capture optimizations into the farm-mode code path will not be merged.
  • Bug fixes should include a regression test or a clear reproduction trace.
  • Be wary of "small refactors": some code is the way it is because of a real bug it's defending against. Read the surrounding context before tidying.
  • New runtime dependencies require a strong justification — the current dep set is intentionally minimal.

License

MIT © Bachamht.

About

Q-Score liquidity-reward farming bot for Polymarket — earns daily liquidity rewards by maintaining persistent, two-sided quotes around the mid-price.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages