Ключевые выводыПараметр market у /prices-history ожидает ID актива (токена CLOB), а не ID рынка или condition ID.
- Параметр
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, а история цен принадлежит токену, а не рынку. Единого ценового ряда у рынка нет, потому что у рынка две стороны.
Ответ намеренно минимален:
{"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 даёт вам оба варианта: id (короткая числовая строка вроде 15345), conditionId (хеш вида 0x…) и clobTokenIds — строку в JSON-кодировке с двумя ID токенов. Это поле нужно декодировать перед использованием, а затем передать один из двух длинных числовых идентификаторов:
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 секунд, — поэтому бэкфилл на уровне сделок остаётся самой медленной частью любого конвейера, и место ему в фоновой задаче. Второй маршрут — слой сабграфов: 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": []}, тогда как более старая история приходит нормально. То есть для архивной глубины он может пригодиться, а вот полагаться на него в свежих данных нельзя. Для надёжной новой истории глубины варианты прежние: записывать вебсокет-поток самостоятельно начиная с сегодня либо покупать у поставщика, который записывал. Узнать об этом после того, как бэктест построен в расчёте на историческую глубину, — дорогой урок; знать на первой неделе — обычное проектное ограничение.
Массовые выгрузки по многим рынкам
Исторические данные для бэктестинга — это тысячи рынков, и здесь однотокенный эндпоинт становится неподходящим инструментом. У CLOB документирован пакетный вариант: POST https://clob.polymarket.com/batch-prices-history с JSON-телом, где markets — список ID активов, максимум 20, плюс start_ts, end_ts и fidelity. Обратите внимание на snake_case против camelCase у одиночного эндпоинта и на то, что fidelity по умолчанию равна 1 минуте.
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 своя строка — 1 000 запросов за 10 секунд, а не общая квота хоста CLOB в 9 000, — и сами лимиты применяются по IP с троттлингом Cloudflare, а не немедленным отказом. При этом ID токенов приходят из Gamma, где ещё строже: у /markets — 300 запросов за 10 секунд. То есть ограничены оба конца конвейера, и группировка важна на обоих. И отдельно, под частый поисковый запрос: истории цен на Gamma как эндпоинта не существует. Gamma несёт текущие outcomePrices и поля изменения за горизонт прямо в объекте рынка, но сам ряд живёт только в CLOB.
Отсюда практический порядок действий: один раз пройти Gamma курсорными keyset-эндпоинтами и собрать локальную таблицу рынков с их ID токенов, закешировать её (эти идентификаторы не меняются), а затем идти по пакетному эндпоинту истории по 20 токенов за раз с той детализацией, которая исследованию действительно нужна: суточные свечи для широкого обзора и мельче — только там, где это важно. Там, где оставшимся ограничением становится объём запросов по большому массиву рынков, бюджеты считаются по IP-адресу, и наши прокси для криптопроектов — это выделенные IPv4-адреса, статичные на весь срок плана, к каждому из которых применяется своя документированная квота. Масштабирование проверяйте на своей нагрузке, а не считайте линейным по умолчанию.