Polymarket Bot Tutorial · Глава 29 из 32
Создайте engine для paper trading Polymarket до выхода в live: симулируйте ордера по реальным ценам, отслеживайте P&L, соблюдайте 30-trade gate (>=55% win rate, +PnL) перед любым live-капиталом — и используйте готовый code skeleton.
Что покрывает эта глава
Paper trading — это обязательный шаг между идеей стратегии и live-развёртыванием. Эта глава — простой paper engine, который служил воротами (gate) для каждого live-бота, что мы выпускали: менее 200 строк Python, он записывает каждую сделку в JSONL-дневник и применяет те же комиссии и slippage, что и live-путь.
- Почему paper всегда идёт перед live
- 30-trade gate (проверенные +55% win rate и положительный PnL)
- Построение простого paper engine
- Ведение paper-дневника параллельно с live-дневником
- Когда paper расходится с live (и почему)
- Переход к live: маленький первый депозит
- Код: минимальный paper engine
Почему paper всегда идёт перед live
30-trade paper gate — это единственная дисциплина, которая отделяет 7,6% прибыльных трейдеров Polymarket от 84,1% тех, кто теряет. Большинство разработчиков пропускают его и платят за обучение собственными деньгами. Честная причина, почему он работает: paper trading раскрывает истинный win rate стратегии на достаточном числе сэмплов, чтобы отличить сигнал от удачи.
Пропуск paper обходится дороже, чем экономит. Стратегия, которая выглядит прибыльной в backtest, но на деле является подбрасыванием монеты, сожжёт $200-500 live-капитала, прежде чем даст 30 сделок live-данных. Прогон тех же 30 сделок на paper стоит $0.
Paper engine не обязан быть сложным. Он обязан быть честным — те же комиссии, тот же slippage, та же задержка исполнения, что и на live-пути. Чем проще, тем лучше: всё необязательное вырезается, и бот выходит в live раньше, чем следовало бы.
30-trade gate (проверенные +55% win rate и положительный PnL)
Gate бинарный: 30 закрытых paper-сделок, заранее записанные критерии успеха (обычно win rate ≥ 55% на стратегии с положительным EV) — или никакого live-развёртывания.
30 — это минимальный размер выборки, при котором 95-процентный доверительный интервал для истинного win rate достаточно узок, чтобы отличить сигнал от шума. Ниже 30 наблюдаемые 60% могут соответствовать истинному значению 45-75%. При 30+ интервал сужается до ~50-70% — всё ещё широко, но достаточно, чтобы исключить вариант «стратегия — подбрасывание монеты».
Критерии успеха должны быть заданы ДО старта paper-прогона. Установка их постфактум порождает рационализацию задним числом (вы найдёте способ истолковать любые 30 сделок как «достаточно хорошие»).
Построение простого paper engine
Paper engine — это, по сути, код live-торговли с заменой функции выставления ордера на симулированное исполнение. Симуляция:
- Чтение живого order book: тот же вызов, что сделал бы live-бот.
- Симуляция исполнения: при покупке через FOK с ценой ≥ best ask исполните ордер по средневзвешенной цене съеденных asks; запишите исполнение в paper-дневник.
- Применение комиссий: вычтите те же комиссии, что заплатил бы live-путь.
- Учёт инвентаря: ведите параллельный paper-баланс и словарь paper-позиций.
Весь engine умещается в 100-200 строк Python. Ключевая дисциплина: каждое допущение, которое делает live-путь (доля исполнения, задержка, комиссия), должно быть воспроизведено на paper, даже если чуть хуже реальности — paper должен быть полом, а не потолком.
Ведение paper-дневника параллельно с live-дневником
Paper-прогон создаёт JSONL-дневник, по структуре неотличимый от live-дневника, который бот будет писать позже. Те же поля: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.
Есть две причины использовать один и тот же формат. Во-первых, инструменты анализа, читающие live-сделки (отчёты PnL, калькуляторы win rate), работают на paper без изменений. Во-вторых, сравнение paper и live позже ловит расхождения, указывающие на баги.
Совет для продакшена: пусть paper engine пишет в per_trade_paper.jsonl в той же папке, что и live-файл per_trade.jsonl. Одна команда сравнивает оба: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
Когда paper расходится с live (и почему)
Расхождения между paper и live неизбежны. Три самых частых.
- Slippage: paper исполняется по снимку ask; live проходит по стакану и может исполниться на 1-2 цента хуже на тонких рынках. Решение: симулируйте slippage на paper, добавляя штраф за сделку, равный половине спреда.
- Задержка исполнения: paper исполняется мгновенно; live занимает 200-500 мс, за которые цена может сдвинуться. Решение: симулируйте, выжидая и перечитывая order book перед «исполнением» на paper.
- Adverse selection: paper предполагает, что вы получаете best ask; live конкурирует с другими ботами, которые могли уже снять этот ask. Решение: симулировать сложнее; честно признайте себе, что paper переоценивает результат.
Когда paper показывает +5%/месяц, а live идёт на -2%/месяц, разрыв обычно в одном из этих пунктов. Аудируйте их по одному, а не предполагайте, что ошиблась сама стратегия.
Переход к live: маленький первый депозит
Paper прошёл 30 сделок. План live-развёртывания:
- Внесите $25-50 как капитал для дымового теста. Считайте его платой за обучение; если потеряете — урок того стоил.
- Запустите бота в live-режиме на 5-10 сделок с позициями минимального размера (5 shares).
- Проверьте, что каждое исполнение совпадает с ожиданиями paper в пределах 2 центов. Любой больший разрыв расследуйте, прежде чем продолжать.
- Если 5-10 live-сделок совпадают с paper, внесите $200-500 и запустите позиции обычного размера.
- Если не совпадают — остановитесь, отладьте, исправьте, начните заново с шага 1.
Самый частый разрыв live-paper при первом развёртывании — пропущенная комиссия или неверная оценка slippage. Их исправить несложно; дисциплина в том, чтобы поймать разрыв до масштабирования капитала.
Код: минимальный paper engine
Образец: простой paper engine, который читает живой order book и симулирует FOK-исполнение.
import json, time
PAPER_BAL = 10_000.0 # USD starting
positions = {} # token_id -> shares
def paper_fok_buy(token_id, max_price, size):
book = fetch_book(token_id)
# Walk asks, fill what we can within max_price
filled = 0; cost = 0
for level in book.asks:
px = float(level["price"])
if px > max_price: break
avail = float(level["size"])
take = min(avail, size - filled)
filled += take
cost += take * px
if filled >= size: break
if filled < size:
return {"status":"rejected","filled":0} # FOK semantics
global PAPER_BAL
PAPER_BAL -= cost
positions[token_id] = positions.get(token_id, 0) + filled
log_paper({"ts": int(time.time()), "action":"buy",
"token": token_id, "size": filled, "price": cost/filled})
return {"status":"matched","filled":filled,"cost":cost}
Дополнения для продакшена: paper-функция продажи (зеркало покупки), симуляция paper GTC (выставить на стакан по цене, симулировать исполнение, когда mid достигает цены), сверка paper-дневника с «как было бы» live-дневником.





