П/ВИН

ArbScanner: калькулятор, фильтры и торговый движок

·8 мин чтения
ArbScanner: калькулятор, фильтры и торговый движок

Если ты когда-нибудь смотрел на таблицу арбитражных возможностей и думал «окей, вилка найдена, но что конкретно мне делать?» — ты понимаешь, с чего всё началось. ArbScanner умел находить расхождения между Polymarket, Kalshi и PredictIt, но дальше начиналась ручная работа: считай в голове, куда ставить, сколько, на какой исход, какой профит в худшем сценарии. Плюс в таблице спокойно висели вилки с экспирацией в 2027 году — то есть деньги придётся морозить на год+, и никакого способа это отфильтровать не было.

Мы это починили. А заодно заложили фундамент для автоматической торговли.

Что было не так с калькулятором

Старый detail-panel.tsx был честным, но бесполезным. Он показывал примерно вот что:

Stake on Polymarket: $52.10
Stake on Kalshi: $47.90
Total: $100
Payout: $104.30
Profit: $4.30

На первый взгляд — вся информация есть. На практике — ты не знаешь:

  • На какую сторону ставить (Yes или No)?
  • Сколько контрактов купить по какой цене?
  • Что произойдёт, если выиграет исход A? А если B?
  • Какой реальный ROI с учётом комиссий обеих платформ?
  • Есть ли ссылка на конкретный маркет, чтобы не искать вручную?

При работе с реальными деньгами эти вопросы критичны. Ошибиться со стороной ставки — значит удвоить риск вместо хеджа.

Новый калькулятор: пошаговая инструкция

Переделали панель с нуля. Теперь она строится вокруг двух карточек-шагов и таблицы сценариев.

Шаг 1 и Шаг 2 — по одному на каждую платформу:

// Структура шага в новом detail-panel.tsx
interface StepCardProps {
  step: number;
  platform: string;
  side: 'YES' | 'NO';        // явно указываем сторону
  stake: number;             // сумма ставки в долларах
  contractPrice: number;     // цена одного контракта
  contractCount: number;     // stake / contractPrice
  marketUrl: string;         // прямая ссылка на маркет
  fee: number;               // комиссия платформы
}

Пользователь видит: «Шаг 1: зайди на Polymarket, купи YES, потрать $52.10 = 100 контрактов по $0.52, ссылка вот». Никакой двусмысленности.

Таблица сценариев — главное нововведение:

| Сценарий | Выплата | Комиссия | Нетто-профит | |---|---|---|---| | YES выигрывает | $100.00 | $3.20 | +$4.70 | | NO выигрывает | $100.00 | $2.80 | +$4.30 |

Внизу — гарантированный профит (worst case из двух сценариев) и ROI. Теперь человек видит, что в любом из исходов он в плюсе — это и есть суть арбитража.

Дополнительно добавили блок рисков прямо в панель: дата экспирации, уровень confidence матча, предупреждение если маркеты могут резолвиться по разным правилам.

Проблема matching/rules risk

Вот что важно понимать про арбитраж между предикшн-маркетами. Кажется, что если Polymarket говорит «No» стоит $0.48, а PredictIt за того же кандидата даёт $0.52 — это чистая вилка. Но это не всегда так.

Формулировки рынков могут отличаться. Один маркет может быть про «официального номинанта», другой — про «победителя праймериз». Дедлайны могут не совпадать. Один маркет может уже войти в странное состояние ликвидности или settlement delay.

Поэтому в калькулятор добавили match_type с тремя градациями:

  • exact (✓) — одинаковые условия резолюции, одинаковые дедлайны
  • probable (≈) — очень похожие, но есть нюансы
  • speculative (?) — формально похоже, но проверяй вручную

Это не устраняет риск полностью, но делает его видимым.

Фильтрация по дате экспирации

Вторая крупная задача — прокинуть end_date из маркетов в интерфейс.

Данные об экспирации были в ответах API всех платформ, но до фронтенда не доходили. Тип Opportunity в types.ts не содержал end_date. Исправили в два слоя.

Бэкенд — в scan route добавили маппинг:

// scan/route.ts — до фикса
const opportunity: Opportunity = {
  id: `${polyMarket.id}_${kalshiMarket.id}`,
  spread: calculateSpread(polyPrice, kalshiPrice),
  // end_date отсутствовал
};
 
// После
const opportunity: Opportunity = {
  id: `${polyMarket.id}_${kalshiMarket.id}`,
  spread: calculateSpread(polyPrice, kalshiPrice),
  end_date: polyMarket.end_date ?? kalshiMarket.end_date ?? null,
  days_to_expiry: getDaysToExpiry(polyMarket.end_date),
};

Отдельно добавили хелпер для инференса даты из текста вопроса — Polymarket иногда не возвращает end_date явно, но в названии маркета написано «...by November 2026»:

// Инferring end_date из текста вопроса
function inferEndDateFromQuestion(question: string): string | null {
  const yearMatch = question.match(/\b(202[4-9]|203\d)\b/);
  if (yearMatch) {
    return `${yearMatch[1]}-12-31`; // консервативная оценка
  }
  return null;
}

Фронтенд — новая колонка «Expires» в таблице с умным форматированием:

  • 3d — если до экспирации меньше недели
  • Mar 15 — если в текущем году
  • Jan 5 '27 — если в следующем году и дальше

Фильтр «Max Expiry» в сайдбаре с вариантами: 1 неделя / 1 месяц / 3 месяца / 6 месяцев / 1 год / без ограничений. По умолчанию — 3 месяца, чтобы вилки на 2027 год не захламляли экран.

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

APY вместо голого процента

Профит 2% звучит по-разному в зависимости от того, на сколько заморожены деньги. Две недели — это 52% годовых. Год — это просто 2%. Добавили колонку APY:

function calculateAPY(netPct: number, daysToExpiry: number): number {
  if (daysToExpiry <= 0) return 0;
  return (netPct / daysToExpiry) * 365;
}

Теперь таблица sortable по APY, и это меняет картину. Вилка на 3 дня с профитом 1.5% выглядит привлекательнее вилки на 180 дней с профитом 4% — когда видишь, что первая даёт 180% годовых, а вторая — 8%.

Фильтрация устаревших маркетов

Отдельно почистили мусор. Маркеты, у которых end_date < now, фильтруются на уровне venue — ещё до попадания в список opportunities. Это касается всех четырёх источников данных. Expired маркеты больше не показываются вообще.

Добавили статусные бейджи:

  • STALE — мало ликвидности, цены могут не исполниться
  • SOON — менее 24 часов до закрытия, нужно действовать быстро

И колонку Max $ — максимальный размер ставки исходя из доступной ликвидности в ордербуке. Это предотвращает ситуацию, когда калькулятор показывает «поставь $500», а в стакане есть только $80.

Торговый движок: от сканера к боту

После того как сканер стал действительно информативным, встал следующий вопрос: а почему нажимать кнопки вручную? Посмотрели на кейсы из исследований — боты на Polymarket зарабатывают реальные деньги. Бот на базе Claude заработал $14K за 48 часов. Аноним с whale tracker вытащил $75K за день на политическом маркете.

Решили строить execution layer. Но сразу с прицелом на бюджеты $10K+, а не на игрушечные $100. Это меняет архитектурные требования принципиально:

  • При мелких суммах можно держать логику в Next.js API routes
  • При $10K+ latency стоит реальных денег: незаполненная нога на $5K — это потеря
  • Нужны persistent WebSocket соединения к CLOB, а не REST polling
  • Нужен kill switch, который останавливает торговлю мгновенно, а не через минуту
  • Partial fill на одной ноге при больших суммах — штатная ситуация, которую нужно обрабатывать

Выбрали Approach B — отдельный Trading Engine на Node.js, который общается с UI через SSE (Server-Sent Events). Next.js остаётся фронтендом и контрольной панелью, Python-сервис — мозгом торговли.

Текущий статус — реализован paper trading модуль: симуляция стратегий на виртуальном балансе $1000. Все стратегии можно включать/выключать из UI, настраивать sizing и минимальную ликвидность. История сделок с P&L, цветовая кодировка результатов. Все данные хранятся в Supabase — переживают редеплои.

Git-история показывает, насколько активно шла работа:

  • Исправлен condition_id маппинг для резолюции сделок
  • Добавлена дедупликация opportunities
  • Починен race condition в daily spend limit
  • CLOB bid price вместо синтетической mid-price
  • PredictIt $850 liquidity cap в калькуляторе

Инфраструктура деплоя

Деплой через Dokploy на arbscanner.pashavin.ru. Небольшое приключение случилось с вебхуком GitHub → Dokploy — он возвращал 401, потому что refresh token устарел. Пришлось тригернуть деплой вручную через API:

curl -X POST https://dokploy.pashavin.ru/api/application.deploy \
  -H "Authorization: Bearer [СКРЫТО]" \
  -H "Content-Type: application/json" \
  -d '{"applicationId": "arbscanner"}'

После этого статус вернул done, https://arbscanner.pashavin.ru/ отвечает 200, API возвращает end_date в opportunities. Вебхук нужно починить отдельно — обновить токен.

Технологии и ссылки

В проекте используются:

  • Polymarket CLOB API — для получения цен и исполнения ордеров
  • Kalshi Trading API — второй основной источник маркетов
  • Next.js App Router — фронтенд и API routes
  • Supabase — хранение позиций и истории сделок
  • Dokploy — деплой и оркестрация

Итог и что дальше

За несколько итераций сканер превратился из «вот таблица цифр» в инструмент, которым реально можно пользоваться. Калькулятор теперь отвечает на конкретный вопрос: что именно делать прямо сейчас, шаг за шагом. Фильтр по дате убирает шум — маркеты с экспирацией через год не засоряют экран. APY колонка меняет приоритизацию: быстрые маленькие вилки часто лучше долгих больших.

Но самое важное — это архитектурное решение строить execution layer с расчётом на масштаб. Много проектов такого рода начинают с «сделаем просто» и потом переписывают всё, когда деньги становятся серьёзными. Мы сразу заложили отдельный trading engine, Supabase для персистентности, kill switch как первоклассную фичу.

Paper trading сейчас работает в продакшне и собирает данные. Следующий шаг — live trading с маленьким реальным бюджетом, верификация что execution работает как ожидается, потом масштабирование. Whale tracking и AI-estimates из исследовательского плана остаются в очереди — они дают дополнительный сигнал, но execution без них уже работает.

Главный урок: арбитраж на предикшн-маркетах выглядит просто на графиках, но дьявол в деталях — matching risk, liquidity depth, settlement timing. Инструмент должен делать эти детали видимыми, а не прятать их за красивым числом "профит 3.2%".

Паша Вин
Паша Вин

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.