# Как выгрузить историю цен из CLOB Polymarket

> Большинство пустых ответов эндпоинта истории цен Polymarket вызвано одной причиной: параметр с именем `market` не хочет ID рынка. Вторая по частоте — форма запроса, не подходящая к диапазону, который вы спрашиваете.

- Источник: https://papaproxy.net/ru/blog/polymarket-historical-data.php
- Опубликовано: 2026-08-09
- Автор: Alex Young
- Рубрика: API и данные · Блог PapaProxy.net

---

## Ключевые выводы

- Параметр `market` у `/prices-history` ожидает **ID актива (токена CLOB)**, а не ID рынка или condition ID.
- Различайте виды отказа: недопустимые фильтры отклоняются кодом `400`, а корректно составленный запрос по токену, которым не торговали, возвращает `{"history": []}` с кодом 200.
- У каждого рынка два токена и два отдельных ряда; берите их из поля `clobTokenIds` в Gamma, которое приходит строкой в JSON-кодировке и требует декодирования.
- `interval` и пара `startTs`/`endTs` взаимоисключающи: для выгрузок задавайте явный диапазон и проверяйте покрытие, а не переходите на крупную детализацию, когда ответ пришёл скудным.
- `fidelity` у `/prices-history` — целое число минут; отдельный `GET /ohlc` с перечислимой fidelity от `1m` до `1w` фигурирует в справочнике ошибок, но в основном индексе API ещё не представлен — проверьте его контракт, прежде чем полагаться.
- Ценовой ряд состоит только из `{t, p}` — ни объёма, ни OHLCV, — поэтому свечи описывают наблюдаемый ряд, а не ленту сделок; данные уровня сделок берутся из `/trades` в Data API при 200 запросах за 10 секунд.
- История глубины ненадёжна: унаследованный `/orderbook-history` лежит вне поддерживаемой поверхности API, и сообщается, что для окон после февраля 2026 года он ничего не возвращает, — записывайте вебсокет сами или берите у архивного поставщика.
- Для массовых задач используйте `POST /batch-prices-history` — до 20 ID активов за вызов; у `/prices-history` квота 1 000 запросов за 10 секунд, у `/markets` в Gamma всего 300, поэтому группировать нужно на обоих концах.

## Что принимает эндпоинт prices-history

Эндпоинт истории цен — `GET https://clob.polymarket.com/prices-history`, публичный и без аутентификации, как и вся читающая поверхность CLOB. Он принимает четыре параметра, и первый — то место, где ломается почти всё:

| Параметр | Тип | Значение |
| --- | --- | --- |
| `market` | string, обязательный | **ID актива** — токен CLOB, а не рынок |
| `startTs` | number | Unix-время, элементы после этой точки |
| `endTs` | number | Unix-время, элементы до этой точки |
| `interval` | enum | `max`, `all`, `1m`, `1w`, `1d`, `6h`, `1h` |
| `fidelity` | integer | Разрешение в **минутах** |

Первую строку стоит перечитать, потому что название действительно вводит в заблуждение, и документация эндпоинта говорит об этом прямо: параметр под именем `market` ожидает asset id. В модели данных Polymarket у рынка два токена исходов — Yes и No, — и у каждого свой CLOB token ID, а история цен принадлежит токену, а не рынку. Единого ценового ряда у рынка нет, потому что у рынка две стороны.

Ответ намеренно минимален:

JSONКопировать код

```json
{"history": [{"t": 1754400000, "p": 0.63}, {"t": 1754403600, "p": 0.65}]}
```

Unix-время и цена от 0 до 1, где цена читается как подразумеваемая вероятность. Обратите внимание, чего здесь нет: ни объёма, ни открытия, максимума и минимума, ни числа сделок. Это ряд наблюдений цены, а не OHLCV, — и это различие определяет весь следующий раздел.

Ещё две механики, которые стоит знать до написания цикла. `interval` и пара отметок времени — альтернативы, а не дополнения: либо `interval` для относительного окна, заканчивающегося сейчас, либо `startTs`/`endTs` для абсолютного диапазона. И `fidelity` задаётся в минутах, поэтому `fidelity=60` означает часовые точки, а `fidelity=1440` — суточные, а вовсе не «количество точек», как этот параметр подписывают некоторые сторонние обёртки.

## Две причины, по которым ответ приходит пустым

Эндпоинт исторических цен может отказать двумя разными способами, и различать их полезно. Некорректные или недопустимые фильтры отклоняются: справочник ошибок документирует ошибки валидации для `market`, `startTs`, `endTs` и `fidelity`, возвращаемые как `400 Bad Request`. А вот запрос, *синтаксически* верный и просто называющий токен, которым никогда не торговали, — или окно, в котором ничего нет, — вернётся как `{"history": []}` с кодом 200. То есть пустой массив не является гарантированным признаком неверного идентификатора: это ответ на корректно сформулированный вопрос, за которым нет данных.

**Причина первая: вы передали ID рынка вместо ID токена.** Это ловушка имени из предыдущего раздела. [Объект рынка в Gamma](/ru/blog/polymarket-three-apis.php) даёт вам оба варианта: `id` (короткая числовая строка вроде `15345`), `conditionId` (хеш вида `0x…`) и `clobTokenIds` — **строку в JSON-кодировке** с двумя ID токенов. Это поле нужно декодировать перед использованием, а затем передать один из двух длинных числовых идентификаторов:

PythonКопировать код

```python
import json, requests

GAMMA = "https://gamma-api.polymarket.com"
CLOB = "https://clob.polymarket.com"

def token_ids(slug):
    r = requests.get(f"{GAMMA}/markets", params={"slug": slug}, timeout=15)
    r.raise_for_status()
    market = r.json()[0]
    outcomes = json.loads(market["outcomes"])          # например, ["Yes", "No"]
    tokens = json.loads(market["clobTokenIds"])        # JSON-строка, а не список
    return dict(zip(outcomes, tokens)), market["closed"]

def price_history(token_id, start_ts=None, end_ts=None, fidelity=60, interval=None):
    params = {"market": token_id, "fidelity": fidelity}
    if interval:                                        # относительное окно, заканчивающееся сейчас
        params["interval"] = interval
    else:                                               # абсолютный диапазон — предпочтителен для бэкфилла
        params["startTs"], params["endTs"] = start_ts, end_ts
    r = requests.get(f"{CLOB}/prices-history", params=params, timeout=30)
    r.raise_for_status()                                # 400 при недопустимых фильтрах
    return r.json().get("history", [])
```

**Причина вторая: форма запроса не подходит к диапазону.** `interval` и пара отметок времени взаимоисключающи, и для исторических выгрузок брать следует именно абсолютный диапазон. В клиентском репозитории Polymarket описан случай, когда разрешившийся рынок при `interval=max` отдавал данные на крупной детализации и пустой массив на мелкой, — причём сам автор в продолжение сообщил, что перешёл на явные `startTs`/`endTs`. Считайте это уроком про форму запроса, а не правилом хранения: для закрытых рынков задавайте диапазон явно, вокруг периода, когда рынок действительно торговался, и затем проверяйте покрытие, сопоставив число полученных точек с длиной запрошенного отрезка. Не зашивайте крупную детализацию как универсальный запасной вариант — так вы выбросите детализацию, которая вполне может быть доступна.

Третья, более простая причина: у рынка, на котором никогда не торговали, истории нет. Прежде чем считать что-то сломанным, посмотрите `volumeNum` в объекте Gamma.

## Как построить свечи из ценового ряда

Поскольку ответ несёт только отметки времени и цены, исторические ценовые данные здесь — это не OHLCV, и добросовестно превратить их в OHLCV нельзя. Свечи построить можно, но нужно понимать, что именно они означают.

Прежде чем строить своё, проверьте, нужно ли. Справочник ошибок Polymarket документирует эндпоинт `GET /ohlc`, принимающий `asset_id`, `startTs`, `limit` и перечислимую `fidelity` со значениями `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1d`, `1w` — стиль параметров отличается от `/prices-history`, где fidelity задаётся целым числом минут. Оговорка в том, что в основном справочнике API он пока не представлен наравне с остальными эндпоинтами рыночных данных, поэтому считайте его контракт неподтверждённым: прощупайте, сравните одно окно с `/prices-history` и не стройте на нём продовый код, пока не проверите форму ответа сами.

Если же строите свечи из ценового ряда, понимайте, что именно они означают. Задав достаточно мелкую `fidelity`, вы можете сгруппировать последовательные наблюдения в корзины и взять первое, максимум, минимум и последнее внутри каждой. Это даст открытие, максимум, минимум и закрытие **наблюдаемого ряда**, а не ленты сделок: если между двумя наблюдениями произошёл всплеск, для ваших свечей его не существовало. Объёма в этих данных нет вовсе — всё, чему он нужен, придётся брать на уровне сделок, о чём дальше.

Два практических замечания. Цены здесь — вероятности в диапазоне 0–1, поэтому доходности и волатильность, посчитанные по ним, ведут себя не так, как по ценам активов: движение с 0,02 до 0,04 — это удвоение, а у границ ряд сжимается. И два токена рынка почти дополняют друг друга: ряды Yes и No должны примерно суммироваться в 1, а зазор между ними — артефакт спреда, а не сигнал. Выгрузить оба и проверить сумму — дешёвая проверка ваших идентификаторов: если она далека от единицы, вы, скорее всего, держите токены двух разных рынков.

## Спуститься ниже цен — к отдельным сделкам

Когда агрегированных цен недостаточно, исторические сделки доступны — но с другого хоста. Эндпоинт `/trades` в Data API возвращает исполненные сделки и принимает фильтры, включая condition ID рынка, поэтому естественный путь такой: Gamma за condition ID, затем Data за исполнениями. Бюджет у него жёстче, чем у CLOB, — 200 запросов за 10 секунд, — поэтому бэкфилл на уровне сделок остаётся самой медленной частью любого конвейера, и место ему в фоновой задаче. Второй маршрут — [слой сабграфов](/ru/blog/polymarket-graphql-api.php): orderbook-сабграф индексирует ончейн-события исполнения, что лучше подходит для агрегатного анализа, чем для запросов по отдельным рынкам. Одна оговорка: публичный манифест сабграфа по-прежнему перечисляет исходные контракты CTF Exchange, тогда как в 2026 году Polymarket перешла на CLOB V2 с новыми контрактами биржи, — поэтому старое развёртывание может не содержать полной свежей истории исполнений. Проверьте, какие контракты индексирует развёртывание, прежде чем считать его авторитетным.

Историческая глубина стакана — самый мутный угол. На CLOB существует унаследованный эндпоинт `GET /orderbook-history`: он фигурирует в справочнике ошибок Polymarket и принимает `asset_id`, `startTs`, `endTs`, `limit` и `offset`, — но он не входит в поддерживаемую основную поверхность API, а в баг-репорте от февраля 2026 года против одного из торговых фреймворков задокументировано, что для любого окна после примерно 20 февраля 2026 года он возвращает `{"count": 0, "data": []}`, тогда как более старая история приходит нормально. То есть для архивной глубины он может пригодиться, а вот полагаться на него в свежих данных нельзя. Для надёжной новой истории глубины варианты прежние: [записывать вебсокет-поток](/ru/blog/polymarket-websocket-api.php) самостоятельно начиная с сегодня либо покупать у поставщика, который записывал. Узнать об этом после того, как бэктест построен в расчёте на историческую глубину, — дорогой урок; знать на первой неделе — обычное проектное ограничение.

## Массовые выгрузки по многим рынкам

Исторические данные для бэктестинга — это тысячи рынков, и здесь однотокенный эндпоинт становится неподходящим инструментом. У CLOB документирован пакетный вариант: `POST https://clob.polymarket.com/batch-prices-history` с JSON-телом, где `markets` — список ID активов, **максимум 20**, плюс `start_ts`, `end_ts` и `fidelity`. Обратите внимание на snake_case против camelCase у одиночного эндпоинта и на то, что `fidelity` по умолчанию равна 1 минуте.

PythonКопировать код

```python
import requests

def batch_history(token_ids, start_ts, end_ts, fidelity=1440):
    assert len(token_ids) <= 20                    # документированный потолок
    r = requests.post(f"{CLOB}/batch-prices-history",
                      json={"markets": token_ids, "start_ts": start_ts,
                            "end_ts": end_ts, "fidelity": fidelity},
                      timeout=60)
    r.raise_for_status()
    return r.json()
```

Это двадцатикратное сокращение числа запросов за те же данные и самое действенное, что можно сделать для массовой выгрузки истории. Обход 5 000 рынков — то есть 10 000 токенов, поскольку у каждого рынка их два, — падает с 10 000 запросов до 500.

Как рассчитать остальное: у `/prices-history` в [таблице лимитов Polymarket](/ru/blog/polymarket-api-rate-limits.php) своя строка — 1 000 запросов за 10 секунд, а не общая квота хоста CLOB в 9 000, — и сами лимиты применяются по IP с троттлингом Cloudflare, а не немедленным отказом. При этом ID токенов приходят из Gamma, где ещё строже: у `/markets` — 300 запросов за 10 секунд. То есть ограничены оба конца конвейера, и группировка важна на обоих. И отдельно, под частый поисковый запрос: истории цен на Gamma как эндпоинта не существует. Gamma несёт текущие `outcomePrices` и поля изменения за горизонт прямо в объекте рынка, но сам ряд живёт только в CLOB.

Отсюда практический порядок действий: один раз пройти Gamma курсорными keyset-эндпоинтами и собрать локальную таблицу рынков с их ID токенов, закешировать её (эти идентификаторы не меняются), а затем идти по пакетному эндпоинту истории по 20 токенов за раз с той детализацией, которая исследованию действительно нужна: суточные свечи для широкого обзора и мельче — только там, где это важно. Там, где оставшимся ограничением становится объём запросов по большому массиву рынков, бюджеты считаются по IP-адресу, и наши [прокси для криптопроектов](/ru/individual.php) — это выделенные IPv4-адреса, статичные на весь срок плана, к каждому из которых применяется своя документированная квота. Масштабирование проверяйте на своей нагрузке, а не считайте линейным по умолчанию.
