> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polysimulator.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

> Production-grade patterns for building reliable trading bots.

# Best Practices

Battle-tested patterns to make your bots production-ready.

***

## 1. Always Use String Numerics

<Warning>
  Never send numeric values as JSON numbers. The API returns and expects all monetary values as **strings** to prevent IEEE 754 floating-point precision loss.
</Warning>

```python theme={null}
# ✅ Correct
payload = {"quantity": "10", "price": "0.42"}

# ❌ Wrong — may lose precision
payload = {"quantity": 10, "price": 0.42}
```

Use `Decimal` for all arithmetic:

```python theme={null}
from decimal import Decimal

price = Decimal(response["price"])
quantity = Decimal(response["quantity"])
cost = price * quantity  # Exact arithmetic
```

***

## 2. Use Idempotency Keys

Every `POST /v1/orders` request should include an `Idempotency-Key` header to prevent duplicate orders on network retries:

```python theme={null}
import hashlib

def make_idempotency_key(market_id, side, outcome, quantity):
    """Generate a deterministic key for this exact order intent."""
    raw = f"{market_id}:{side}:{outcome}:{quantity}"
    return hashlib.sha256(raw.encode()).hexdigest()[:32]
```

<Tip>
  If you retry a request with the same idempotency key, the server returns the original response without creating a duplicate order.
</Tip>

***

## 3. Prefer Batch Endpoints

When operating on multiple markets, use batch endpoints to reduce HTTP overhead and stay within rate limits:

| Instead of...                   | Use...                   |
| ------------------------------- | ------------------------ |
| N × `GET /v1/markets/{id}`      | `POST /v1/prices/batch`  |
| N × `POST /v1/orders`           | `POST /v1/orders/batch`  |
| N × `GET /v1/markets/{id}/book` | Cache + periodic refresh |

```python theme={null}
# ✅ Fetch 20 prices in one call
resp = requests.post(
    f"{BASE_URL}/v1/prices/batch",
    headers=HEADERS,
    json={"market_ids": market_ids},
    timeout=10,
)
resp.raise_for_status()
prices = resp.json()
```

***

## 4. Use WebSocket Over REST Polling

For price monitoring, WebSocket streaming is vastly more efficient:

|                 | REST Polling (5s) | WebSocket    |
| --------------- | ----------------- | ------------ |
| Latency         | 0–5,000 ms        | \<100 ms     |
| API calls/hr    | 720/market        | 1 connection |
| Rate limit risk | High              | None         |

Reserve REST API for:

* Order placement
* Account queries
* Historical data

***

## 5. Use Cursor Pagination for Large Datasets

For endpoints that return lists (orders, positions, trade history), prefer cursor-based pagination over offset:

```python theme={null}
# ✅ Cursor pagination — consistent results
cursor = None
all_orders = []
while True:
    params = {"limit": 100}
    if cursor:
        params["cursor"] = cursor
    resp = requests.get(f"{BASE_URL}/v1/orders", headers=HEADERS, params=params)
    data = resp.json()
    all_orders.extend(data["orders"])
    cursor = data.get("next_cursor")
    if not cursor:
        break
```

<Warning>
  Offset pagination (`offset=100`) can skip or duplicate items when new records are inserted between pages. Use cursor pagination for trading data.
</Warning>

<Note>
  The result-list key differs by endpoint. The native `GET /v1/orders` returns
  `data["orders"]` (shown above). The PM-shape `GET /v1/data/orders` returns the
  rows under `data["data"]` instead — read the right key for whichever endpoint
  you call, or the loop silently iterates nothing.
</Note>

***

## 6. Cache Market Metadata

Market metadata (question text, outcomes, slugs) changes rarely. Cache it locally and refresh periodically:

```python theme={null}
import time

class MarketCache:
    def __init__(self, ttl=300):
        self.markets = {}
        self.last_refresh = 0
        self.ttl = ttl

    def get(self, market_id):
        if time.time() - self.last_refresh > self.ttl:
            self.refresh()
        return self.markets.get(market_id)

    def refresh(self):
        resp = requests.get(f"{BASE_URL}/v1/markets", headers=HEADERS, params={"limit": 200})
        for m in resp.json():
            self.markets[m["condition_id"]] = m
        self.last_refresh = time.time()
```

***

## 7. Implement Exponential Backoff

Never hammer the API on errors. Use exponential backoff with jitter:

```python theme={null}
import random, time

def backoff_wait(attempt, base=1, cap=30):
    """Exponential backoff with jitter."""
    wait = min(base * (2 ** attempt), cap)
    jitter = random.uniform(0, wait * 0.1)
    return wait + jitter
```

***

## 8. Monitor Your Bot

Use the health and metrics endpoints to monitor the API and your bot:

```python theme={null}
def check_api_health():
    """Verify API is ready before trading.

    GET /v1/health/ready returns status "ok" or "tolerating" with HTTP 200
    when the API can serve, and "degraded" with HTTP 503 when it can't.
    There is no "healthy" status — gate on the 200 + the ok/tolerating set.
    """
    resp = requests.get(f"{BASE_URL}/v1/health/ready")
    if resp.status_code != 200:
        print(f"API not ready (HTTP {resp.status_code}): {resp.text}")
        return False
    status = resp.json().get("status")
    if status not in ("ok", "tolerating"):
        print(f"API not ready: {status}")
        return False
    return True
```

Track your bot's performance via the equity curve:

```python theme={null}
def log_performance():
    """Fetch and log P&L metrics.

    GET /v1/account/balance returns: balance, currency, unrealized_pnl,
    total_value, starting_balance. There is no `pnl` key — total PnL is
    total_value − starting_balance (which is exactly what `unrealized_pnl`
    reports here: cash + open-position value vs the wallet's starting bankroll).
    """
    from decimal import Decimal
    b = requests.get(f"{BASE_URL}/v1/account/balance", headers=HEADERS).json()
    total_pnl = Decimal(b["total_value"]) - Decimal(b["starting_balance"])
    print(
        f"Balance: {b['balance']} | Total value: {b['total_value']} | "
        f"PnL: {total_pnl} | unrealized_pnl: {b['unrealized_pnl']}"
    )
```

***

## 9. Handle Market Resolution

Markets can resolve at any time. Your bot should handle positions being settled:

* Winning positions: payout credited automatically
* Losing positions: position marked `CLOSED` with `$0` value
* Use `GET /v1/account/positions?status=CLOSED` to review settled positions

***

## 10. Test in Virtual Mode First

<Info>
  PolySimulator is paper trading — develop and test your complete bot logic
  with zero financial risk against the same REST surface you'll use in
  production. Switching to live trading on Polymarket is **not** a pure
  base-URL swap, though: PM's CLOB `POST /order` requires EIP-712-signed
  orders (maker/taker amounts, salt, signature). The closest drop-in path is
  PolySimulator's PM-shape `POST /v1/order` + `py-clob-client`, which
  approximates PM's wire format — see
  [CLOB Compatibility](/concepts/clob-compatibility) and
  [Live Migration](/deployment/live-migration) for what changes.
</Info>

```bash theme={null}
# Paper trading on PolySimulator
export POLYSIM_BASE_URL="https://api.polysimulator.com"
export POLYSIM_API_KEY="ps_live_..."
```

***

## Checklist

Use this checklist before deploying your bot:

* [ ] All numeric values sent as strings
* [ ] Idempotency keys on all order requests
* [ ] Exponential backoff on 429/5xx errors
* [ ] `Retry-After` header respected
* [ ] WebSocket reconnection with token refresh
* [ ] Cursor pagination for list endpoints
* [ ] Market metadata cached locally
* [ ] Health check before trading loop starts
* [ ] Logging for all order placements and errors
* [ ] Tested in virtual mode with full strategy
