polysqueeze 0.1.7

Rust SDK for authenticated access to Polymarket's CLOB, Gamma, and WebSocket APIs.
Documentation
### **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.