### **Polymarket Market-Making Bot Strategy**
This document outlines a market-making strategy for the `polysqueeze` trading bot. The primary goal is to capture the bid-ask spread in selected markets while carefully managing risk. The strategy avoids markets prone to sudden, binary price jumps and focuses on those with characteristics suitable for automated market-making.
The strategy is divided into two main components:
1. **Phase 1: Market Discovery and Selection** - A periodic process to identify suitable markets.
2. **Phase 2: Market-Making Loop** - A continuous, real-time process for quoting prices in the selected markets.
---
### **Phase 1: Market Discovery and Selection**
This phase runs periodically (e.g., every 15 minutes) to build a portfolio of markets to trade.
**Objective:** To identify active markets that are liquid, have a sufficiently wide spread, and are unlikely to resolve abruptly.
**Implementation Steps:**
1. **Fetch Active Markets:**
* Use the `ClobClient::get_markets(next_cursor, Some(&GammaListParams { limit: Some(50), ..Default::default() }))` function to fetch all active markets. Implement a loop to handle pagination by passing the `next_cursor` from each response until it is null or indicates the end.
2. **Qualitative Filtering (Risk Aversion):**
* Iterate through the list of `types::Market` structs obtained in the previous step.
* **Filter by Category & Keywords:**
* Analyze `market.question`, `market.description`, and `market.category`.
* **Exclude** markets containing keywords that suggest a binary, imminent outcome. Maintain a configurable blocklist of terms like: `announcement`, `will say`, `today`, `reveal`, `release`.
* **Prioritize** markets from categories that imply more gradual price discovery. Maintain a configurable allowlist of categories like: `Sports`, `Politics`, `Crypto` (for longer-term predictions).
* **Filter by Resolution Time:**
* Parse the `market.end_date_iso`.
* Exclude any market that is set to resolve within a configurable threshold (e.g., `7 days`). This minimizes exposure to last-minute volatility.
3. **Quantitative Filtering (Spread & Liquidity):**
* For the remaining candidate markets, perform a quantitative check using the efficient batch endpoints.
* **Fetch Order Books in Batch:** Use `ClobClient::get_order_books(token_ids: &[String])` to get the `OrderBookSummary` for all candidate markets in a single API call.
* For each `OrderBookSummary`:
* **Liquidity Threshold:**
* Calculate the total liquidity available within the top 5 price levels of both bids and asks.
* A market is viable only if this liquidity exceeds a configurable threshold (e.g., `$2,000`).
* `total_liquidity = book.bids.iter().take(5).map(|l| l.size).sum() + book.asks.iter().take(5).map(|l| l.size).sum()`
* **Spread Threshold:**
* Calculate the spread: `spread = book.asks[0].price - book.bids[0].price`.
* Calculate the mid-price: `mid_price = (book.asks[0].price + book.bids[0].price) / 2`.
* A market is viable only if the spread is wide enough to be profitable. The condition should be `(spread / mid_price) > SPREAD_THRESHOLD` (e.g., `0.04` for a 4% spread).
4. **Final Selection:**
* The markets that pass all the above filters are the designated markets for the market-making loop. The bot will now begin actively quoting them.
---
### **Phase 2: Market-Making Loop**
This phase runs in a tight, continuous loop for each market selected in Phase 1. It requires a real-time data connection.
**Objective:** To maintain a two-sided quote (a bid and an ask) around the mid-price, capturing the spread as other participants trade against our orders, while managing inventory risk.
**Implementation Steps:**
1. **Initialization per Market:**
* For each selected `token_id`, instantiate a local `book::OrderBook` and place it in the `book::OrderBookManager`.
* Use `ClobClient::get_order_book(token_id)` to fetch the initial full state and populate the local `OrderBook`.
* Initialize a `ws::WebSocketStream` and subscribe to the `MARKET` and `USER` channels for the selected `token_id`s to receive real-time updates.
* `stream.subscribe_market_channel(vec![token_id])`
* `stream.subscribe_user_channel(vec![market_condition_id])`
2. **Real-time State Management:**
* Listen to the `WebSocketStream`. On receiving a `StreamMessage`:
* `BookUpdate`: Immediately apply the `OrderDelta` to the local `OrderBook` using `book.apply_delta()`. The client's internal use of `FastOrderDelta` ensures this is highly performant.
* `UserTrade`: A fill on one of our orders. Immediately update the bot's internal inventory tracking for that market.
3. **Core Quoting Logic:**
* This logic is triggered after every `BookUpdate` or a periodic refresh (e.g., every 500ms).
* **Recalculate Fair Value:** Get the latest best bid and ask from the local `OrderBook` and compute the `mid_price`.
* **Determine Bot's Spread:** Define a target spread for the bot's quotes (e.g., `3%`). This is the desired profit margin.
* `our_spread = 0.03`
* **Inventory-based Skew:** Adjust the mid-price based on current inventory to manage risk.
* `skew = inventory_size * SKEW_FACTOR` (e.g., `SKEW_FACTOR = 0.001`).
* `adjusted_mid_price = mid_price - skew`.
* If inventory is positive (long), this lowers the quote prices, making it cheaper to sell to us and more expensive to buy from us, encouraging a return to neutral.
* If inventory is negative (short), this raises the quote prices.
* **Calculate New Quotes:**
* `our_bid_price = adjusted_mid_price * (1 - our_spread)`
* `our_ask_price = adjusted_mid_price * (1 + our_spread)`
* **Define Order Size:** Use a small, fixed order size (e.g., `$20`) to limit exposure on any single trade.
4. **Order Execution and Management:**
* This is the most critical and time-sensitive part of the loop.
* **Fetch Open Orders:** Get a list of the bot's currently active orders in the market.
* **Cancel Stale Orders:** Compare the active orders with the newly calculated `our_bid_price` and `our_ask_price`. If they do not match, cancel them immediately. For maximum speed, a `ClobClient::cancel_all()` call for the market might be faster than targeted cancellation.
* **Place New Orders:**
1. Create a new bid: `ClobClient::create_order()` with `side: Side::BUY`, price: `our_bid_price`, and the fixed size.
2. Create a new ask: `ClobClient::create_order()` with `side: Side::SELL`, price: `our_ask_price`, and the fixed size.
3. Submit both orders using `ClobClient::post_order()`.
5. **Global Risk Management:**
* Maintain a global view of inventory across all markets.
* If the net exposure (sum of all positions) exceeds a global capital limit, the bot should enter a "safe mode" where it stops placing new orders and may even start reducing its largest positions.
* If a single market's inventory exceeds a per-market risk limit (e.g., `$500`), stop quoting that market and flag it for manual review or automated neutralization.