Плейбук: реклама Ozon — данные, оценка окупаемости, решения
Срабатывает, когда пользователь спрашивает про рекламу Ozon: «куда уходит рекламный бюджет», «по каким запросам показываемся / сливаем», «разбей рекламу по типам кампаний», «ДРР по артикулам Ozon», «почему артикул X жрёт рекламу», «поисковые запросы по товару», «окупается ли реклама», «выключать или масштабировать», «почему просели заказы».
Файл делится на три слоя, и путать их не надо:
- A. Данные (§0–§5) — где лежит, чем тянуть, грабли Performance API, что Ozon не даёт.
- B. Методика оценки (§6) — на чём считается вердикт «окупается / нет» и какие решения из данных вообще следуют. Единственный раздел, по которому делается вывод.
- C. Кейсы (§8+) — датированные разборы, только как иллюстрация.
Стек: mp-system (корень), .venv/bin/python, БД mp.db, shop 2 = Мой Ozon, shop 4 =
Siluetta Ozon. Performance API — app/ozon/performance.py (PerformanceClient). Расчёт
прибыли/ДРР — data/notes/ozon/profit-methodology.md. Зрелый выкуп и прогноз хвоста —
data/notes/ozon/orders-ad-cpo-playbook.md (не дублировать сюда).
Быстрый production-отчёт CPL/CPO
Для стандартного read-only запроса сначала использовать report.cmd ads, не ручной SQL:
.\report.cmd ads --shop "Ozon Siluetta"
.\report.cmd ads --shop "Ozon Siluetta" --sku "ELS004 Black" --date yesterday
.\report.cmd ads --shop "Ozon Siluetta" --period "за месяц"
Если период не указан, реклама берётся за последние 7 закрытых дней по Москве. Фильтр
--sku понимает регистр, пробелы и общие алиасы себестоимости (ELS004 Black совпадает с
карточкой ELS04-black). Старые JSON-поля не удаляются.
Дополнительные формулы:
CPL = рекламный расход / рекламные добавления в корзину
атрибутированные заказы = direct orders + model orders (halo)
сырой CPO = рекламный расход / атрибутированные заказы
ожидаемые выкупы = Σ(атрибутированные заказы offer_id × зрелый выкуп offer_id)
CAC ожидаемого выкупа = рекламный расход / ожидаемые выкупы
доступная маржа = 30-дневный вклад после себеса и УСН, но до рекламы / net-выкупы
Зрелый выкуп берётся по 60-дневной когорте, закончившейся 15 дней назад; сначала размер,
при выборке меньше 10 завершённых заказов — артикул. Если и по артикулу меньше 10, вердикт
недостаточно данных, без фолбэка на среднее магазина.
Оценка CAC ожидаемого выкупа относительно доступной маржи:
- до 70% включительно —
вменяемый CPO; - выше 70% и до 100% включительно —
пограничный CPO; - выше 100% —
дорогой/убыточный CPO; - расход есть, но корзин или атрибутированных заказов нет —
нет конверсий; - нет зрелого выкупа, себеса или 30-дневной экономики —
недостаточно данных.
Отчёт не вызывает Performance API. Неполное покрытие периода обозначается
complete=false; после успешного ответа не читать БД и плейбуки повторно.
🔴 Границы применимости этого отчёта. Все его метрики после CPL (сырой CPO,
CAC ожидаемого выкупа, вердикты «вменяемый / пограничный / дорогой») стоят на
атрибутированных заказах Ozon (orders + orders_model). Эта атрибуция не выдерживает
проверки на согласованность (§6.1), поэтому отчёт годится как быстрая диагностика ставки
и сравнение кампаний между собой, но не как вердикт об окупаемости артикула. Вердикт —
только по §6: знаменатель «все корзины / все заказы», метрика CPS против вклада.
CPL (расход / рекламные корзины) от атрибуции не зависит и остаётся валидным.
0. Перед стартом: данные свежие?
Реклама и кампании часто устаревшие — сперва доингест (Performance API асинхронный, тянется минутами):
.venv/bin/python scripts/ingest_ozon.py --shop-id 2 campaigns
.venv/bin/python scripts/ingest_ozon.py --shop-id 2 ad-stats --from YYYY-MM-DD --to YYYY-MM-DD
Кладётся в ozon_ad_metrics_daily (день × SKU × кампания: views/clicks/orders/money/placement)
и ozon_campaigns (тип/плейсмент кампаний).
⚠ Креды Performance. Резолв: shops.perf_cred_path → файл Ozon_Cred.txt (Client ID /
Client Secret), резолвер app/web/shop_status.py::_resolve_ozon_perf. После Win→Linux
миграции в perf_cred_path остаётся мёртвый Windows-путь → «Performance creds не заполнены».
Фикс: UPDATE shops SET perf_cred_path='/home/mi/Bot_TG/mp-system/Ozon_Cred.txt' WHERE id=?.
1. Таксономия: типы кампаний и плейсменты
Тип кампании (ozon_campaigns.adv_object_type / payment_type):
| adv_object_type | payment_type | Что |
|---|---|---|
| SKU | CPC | Оплата за клик (товарная) |
| SEARCH_PROMO | CPO | Оплата за заказ (% с заказа, ДРР-cap) |
| BANNER | CPM | Баннер |
| VIDEO_BANNER | — | Видеобаннер |
| REF_VK / REF_BLOGGER | — | Внешний трафик (VK / блогеры) |
Плейсмент (placement, для CPC):
| placement | По-русски | Фразы? |
|---|---|---|
| PLACEMENT_TOP_PROMOTION | Поиск (топ выдачи) | да (отчёт по фразам только тут) |
| PLACEMENT_SEARCH_AND_CATEGORY | Поиск и рекомендации | частично (только поиск-часть; рекомендации склеены) |
| PLACEMENT_PDP | Карточка товара | нет (только ручные кампании) |
| PLACEMENT_OVERTOP | Поиск и главная (спецразмещение) | — |
| PLACEMENT_TAKEOVER | Первые 4 плитки (спецразмещение) | — |
| None (в ad_metrics) | — | это CPO-операции (за заказ) |
Нет плейсмента «только рекомендации». Рекомендации всегда в связке с поиском (
SEARCH_AND_CATEGORY). Без фраз умеют:PDP(карточка, только ручные) и CPO.
Чего Ozon не даёт вообще (не искать, время не тратить)
- Разбивки «поиск vs рекомендации» внутри кампании.
views/clicksуSEARCH_AND_CATEGORY— одно число на оба размещения. В деньгах сплит доступен только в CPO-режиме «все товары» (all_sku_promo, §4). - Кликов по товару в аналитике карточки. В ответе аналитики (~60 метрик) поля
clicksнет ни в каком виде. Ближайшее по смыслу —pdpViews(просмотр карточки). - Честной атрибуции заказа к рекламе. См. §6.1.
- Минус-слов. Продавец их не ставит; вывод по фразам — только для решения вкл/выкл или ставки.
Сравнение типов РК: «Поиск» против «Поиск и Полки»
Siluetta Ozon (shop 4), все товары, 01.06–28.08.2026:
| Тип РК | Расход | CPC | Клик→корзина | Цена корзины |
|---|---|---|---|---|
SEARCH_AND_CATEGORY (Поиск и Полки) |
345 531 | 1.83 ₽ | 3.10% | 59 ₽ |
TOP_PROMOTION (Поиск / Вывод в топ) |
60 109 | 5.19 ₽ | 3.81% | 136 ₽ |
Помесячно соотношение не плавает (июнь 52/117, июль 64/143, август 58/145 ₽ за корзину). «Поиск» даёт клик качественнее — конверсия в корзину выше на ~23%, — но клик в 2.8× дороже, и качества не хватает, чтобы это отбить: корзина выходит в 2.3× дороже.
⚠ Не резать TOP_PROMOTION автоматически по цене корзины: у неё бывает другая задача
(вывод новинки в топ, удержание позиции). Сначала проверить назначение кампании.
2. Разрез расхода по типам РК (главный запрос)
SELECT placement, payment_type, campaign_id, campaign_title,
ROUND(SUM(money)) AS spend, SUM(clicks) AS clicks, SUM(orders) AS orders
FROM ozon_ad_metrics_daily
WHERE shop_id = :shop AND date BETWEEN :since AND :to
GROUP BY campaign_id ORDER BY SUM(money) DESC;
Чтение: placement=NULL → CPO (за заказ); TOP_PROMOTION → поиск-CPC; SEARCH_AND_CATEGORY
→ поиск+рекомендации-CPC. Свод по типу: GROUP BY placement (или payment_type).
Связка с прибылью: агрегировать SUM(money) per color_key(offer_id) из
ozon_ad_metrics_daily. Исторический образец _oneshot_ozon_unit_shop2.py на 16.08.2026
сохранился только в незакоммиченной рабочей копии Hetzner и не является зависимостью
плейбука. Сверка с finance:
SUM(amount) WHERE operation_type_name IN ('Оплата за клик','Продвижение с оплатой за заказ')
должна совпасть с Performance в пределах 0.1%.
3. Отчёт по поисковым запросам
Только для CPC-кампаний с placement = PLACEMENT_TOP_PROMOTION. Метод на стадии тестирования.
POST /api/client/statistics/phrases/json, тело:
{ "campaigns": ["<id>", ...], "dateFrom": "YYYY-MM-DD", "dateTo": "YYYY-MM-DD", "groupBy": "NO_GROUP_BY" }
Асинхронно → UUID → client.wait_statistics(uuid) → client.statistics_report_bytes(uuid)
(JSON-вариант возвращает dict). Дата не раньше 2025-02-01.
Структура: { "<campaign_id>": { title, report: { rows: [...] } } }, строка =
{sku, title, search_phrase, views, clicks, ctr}.
⚠ Только показы/клики/CTR — НЕТ денег и заказов по фразе. Расход на фразу можно лишь
оценить: клики × средний CPC (CPC = расход_кампании / Σ кликов). Конверсию «фраза→заказ»
API не отдаёт. CTR низкий = плохая релевантность показа.
⚠ Минус-слова продавец на Ozon сам НЕ ставит. Вывод по фразам — для решения вкл/выкл кампании или ставки, не для ручной минусовки.
Алгоритм исторического _oneshot_ozon_phrases_shop2.py: взять TOP_PROMOTION-кампании с
расходом в окне, запросить отчёт, агрегировать фразы по кликам и оценить расход через
средний CPC. На 16.08.2026 этот one-shot есть только в незакоммиченной рабочей копии
Hetzner; воспроизводимый контракт — endpoint и SQL ниже, а не наличие файла.
Кандидаты кампаний:
SELECT DISTINCT campaign_id FROM ozon_ad_metrics_daily
WHERE shop_id=:shop AND date BETWEEN :since AND :to AND placement='PLACEMENT_TOP_PROMOTION' AND money>0;
4. Прочие отчёты Performance (что ещё доступно)
| Нужно | Endpoint | Метод | Отдаёт |
|---|---|---|---|
| Дневная стата по кампаниям | /api/client/statistics/daily |
GET | расход/клики по дням |
| Расход по кампаниям | /api/client/statistics/expense |
GET | быстрый свод трат |
| CPO заказы | /api/client/statistic/orders/generate/json |
POST {from,to} |
заказы CPO (исп. в ingest_cpo_orders) |
| CPO товары (выбранные) | /api/client/statistic/products/generate/json |
POST {from,to} |
per-SKU: orders, ДРР, расход CPO + CPC-доля |
| CPO товары (все, сплит поиск/реком) | /api/client/statistics/all_sku_promo/products/generate/json |
GET timeBounds.from/to |
продажи/заказы из поиска vs из рекомендаций |
| Конкурентные ставки | /api/client/campaign/{id}/products/bids/competitive |
— | ставки конкурентов |
⚠ Сплит «поиск vs рекомендации» (в деньгах/заказах) есть ТОЛЬКО в режиме «все товары»
(all_sku_promo). Если CPO настроен как «выбранные товары» — этот отчёт даёт 404
«кампания не найдена», сплит недоступен (нужно переключить продвижение в кабинете на «все товары»).
⚠ CPO «выбранные товары» расход = CPO (за заказ, ДРР-cap) + CPC (за клик) в одном
инструменте (поля MoneySpent/DRR и MoneySpentFromCPC/ordersFromCPC).
5. Механика Performance API
- Авторизация: OAuth2
client_credentials→ Bearer (TTL ~30 мин), уже вPerformanceClient. - Тяжёлые отчёты асинхронные: submit →
UUID→wait_statistics(uuid, max_wait_sec=600)→statistics_report_bytes(uuid)./json-вариант пути → dict сразу; обычный → CSV/ZIP. - Метод иногда GET, иногда POST — если 405, поменять метод; если параметры через query
(
timeBounds.from) — это GET. Есть rate-limit (раздел «Лимиты» в доке). - Зеркало доки:
Dev-API-Ozon/html/latest/performance/index.md.
NOT_STARTED — норма, не баг
wait_statistics поллит статус UUID. Типичный сценарий: UUID создан → NOT_STARTED 2–5 мин
→ IN_PROGRESS ~30c → OK. 240 секунд — мало, использовать max_wait_sec=600. Реализовано в
pipeline.py: step("ad-stats", ..., max_wait_sec=600).
Лимит 1 одновременная выгрузка на аккаунт → 429 «максимум 1» при попытке создать второй
UUID. Надо дождаться завершения первого, потом повторить. PerformanceClient.request() ретраит
429 3 раза с паузой 30с — если очередь не освобождается за 90с, кидает исключение.
Колонки CSV переименованы Ozon (~2025)
POST /api/client/statistics возвращает ZIP/CSV с заголовками, которые изменились. Актуальные
имена (июнь 2026, verified):
| Старое | Актуальное |
|---|---|
Заказы |
Продано товаров |
В корзину |
Добавления в корзину |
Продажи, ₽ |
Продажи в продвижении, ₽ |
Сумма заказов, ₽ |
Заказано на сумму, ₽ |
Заказы модели |
Продано товаров модели |
Продажи с заказов модели, ₽ |
Продажи в продвижении с заказов модели, ₽ |
Парсер app/ozon/ingest._parse_ad_stats_csv сначала ищет известные точные варианты через
_idx("старое", "новое"), затем использует смысловой fallback по заголовкам:
- количество заказов: содержит
заказ/продано/заказано, но не содержитмодельи денежные маркеры; - сумма заказов: приоритетно
Заказано на сумму, ₽; затем старые поля продаж/сумм. Fallback содержит₽/руб/выруч/сумм/продажи, но не содержитмодельирасход; - количество/сумма модели: те же правила, но обязательно содержит
модель.
Это нужно, потому что Ozon меняет CSV-заголовки без смены endpoint. Если i_orders is None —
в лог уйдёт WARNING "колонка 'Заказы'/'Продано товаров' не найдена".
Диагностика: поднять уровень ozon_ingest до DEBUG и смотреть строку CSV campaign=X headers: [...].
Симптом: все записи
ozon_ad_metrics_daily.orders = 0при ненулевомmoney. Значит колонка renamed и парсер её не нашёл — добавить новое имя в_idx(...).
ingest_campaigns и ARCHIVED-кампании
GET /api/client/campaign возвращает только живые кампании. Архивированные из ответа исчезают,
но в ozon_campaigns остаются со старым статусом (RUNNING/STOPPED/INACTIVE). В результате
ingest_ad_stats посылает statistics_request с невалидными campaign_id → Ozon принимает, UUID
выдаёт, но отчёт НИКОГДА не стартует (NOT_STARTED бесконечно).
Фикс (реализован в ingest_campaigns): после upsert свежих данных кампании, не попавшие в
ответ API, помечаются CAMPAIGN_STATE_ARCHIVED, и ingest_ad_stats их пропускает.
6. Методика оценки: на чём делается вывод
Установлено 28.08.2026 на разборе ELS004 Black (Siluetta Ozon, 05.07–22.08). Заменяет прежний раздел «CPA с поправкой на выкуп», который стоял на атрибуции Ozon.
6.1. Атрибуция Ozon не годится как основа вердикта
Что означают поля ozon_ad_metrics_daily (парсер app/ozon/ingest._parse_ad_stats_csv):
| Поле | Колонка CSV | Смысл |
|---|---|---|
orders |
«Продано товаров» (ex-«Заказы») | заказы самого рекламируемого SKU |
orders_model |
«Продано товаров модели» | заказы других SKU той же модели (гало) |
orders_money |
«Заказано на сумму, ₽» | другая стадия/модель атрибуции |
orders_model_money |
«Продажи в продвижении с заказов модели, ₽» | продажи по гало |
Официальное определение Ozon: «Продано товаров — количество заказов, которые совершили
покупатели, увидев и заказав товары с продвигаемых позиций»
(knowledge-base-mp/ozon/ads/extracted-text/08-promotion-analytics.txt). Несмотря на слово
«Продано», стадия — заказ, а не выкуп.
🔴 orders_money — не денежная пара к orders. Парсер намеренно ставит «Заказано на
сумму» первой. Проверка: ELS004 Black, 14–20.08 — orders_money = 609 390 ₽ ≈ 111 единиц
при orders + orders_model = 38; в отдельные недели orders_money превышает всю валовую
выручку артикула. Считать ДРР = money / orders_money и CPA = money / orders
на одной строке отчёта нельзя — это разные базы.
🔴 Проверка на согласованность, которую атрибуция не проходит. Если принять её за истину, то для ELS004 Black получается: рекламная корзина конвертится в заказ на 19%, органическая — на 50–66%, и цифра скачет 29% → 66% неделя к неделе. Корзина не знает, из какого плейсмента пришёл покупатель — такой разрыв нефизичен. Вывод: раскладывать заказы на «рекламные» и «органические» по данным Ozon нельзя.
⚠ Гало задваивается: если на одну карточку работают кампании на M и на S, заказ размера S по
клику в кампании M попадёт в orders_model кампании M, и наоборот. Суммировать гало по
кампаниям одной модели — с оговоркой.
6.2. Органика не контрольная группа
Инкрементальность «реклама против органического baseline» для наших карточек не считается: органический трафик не независим от рекламы — включили РК, органика появилась; выключили, через неделю пропала.
Подтверждение на ELS004 Black, 11 недель 05.06–20.08:
- корреляция недельный расход ↔ все заказы r = 0.94; клики РК ↔ все заказы r = 0.97;
- разгон июня: расход 1.6 → 14.5 тыс ₽, заказы 26 → 110 — движение одной пары, а не «реклама плюс независимая органика»;
- обратный эпизод 29.07–06.08: показы в поиске рухнули с ~5 000 до ~1 000 в день на девять
дней, а
totalViewsне просел — дыру закрыла реклама. Именно на этой неделе лучший CPS.
⚠ r = 0.94 — это корреляция, а бюджет двигали не случайно. Отделить вклад рекламы от сезона наблюдением нельзя в принципе; нужен эксперимент (§6.6).
6.3. Знаменатель — всё
Из 6.1 и 6.2 следует единственный корректный знаменатель: все корзины и все заказы товара, без попытки выделить рекламную долю.
CPL (цена корзины) = расход / рекламные корзины (atbs) # атрибуция не нужна
CPO = расход / ВСЕ заказы товара
CPS (стоимость продажи) = расход / продажи после возвратов # ← вердикт считается по нему
ДРР = расход / выручка продаж после возвратов
Продажи после возвратов = отправления в статусе delivered минус те, по которым есть
финансовая операция «Получение возврата, отмены, невыкупа от покупателя». На Ozon FBO нет
отдельного статуса returned: отмена и невыкуп при доставке схлопнуты в cancelled, а
возврат после выкупа виден только в ozon_finance_operations. По ELS004 Black за 01.07–27.08
это 27 шт из 263 выкупленных = 10% сверх невыкупа.
⚠ Когорта должна дозреть. Пока в неделе много отправлений вне delivered/cancelled,
продажи и CPS ещё уедут — такую неделю помечать, а не сравнивать. Правило зрелости —
orders-ad-cpo-playbook.md.
6.4. Порог: вклад до рекламы
Единственный абсолютный эталон. Считается по факту ozon_finance_operations, привязанным к
отправлениям артикула, за окно ≥1 месяца:
нетто с Ozon = «Доставка покупателю»
− «Получение возврата, отмены, невыкупа от покупателя»
− «Доставка и обработка возврата, отмены, невыкупа»
− «Оплата эквайринга» − упаковка партнёрами и материалы
нетто на единицу = нетто с Ozon / (проданные единицы − возвращённые)
вклад до рекламы = нетто на единицу − себес (CostBook) − УСН 6% от покупательской цены
Отмены и невыкупы уже внутри: их стоимость размазана на те единицы, что доехали.
Пример, ELS004 Black, 01.07–27.08.2026: нетто 713 320 ₽ на 321 нетто-проданную единицу = 2 222 ₽/шт; − себес 850 − УСН 183 = вклад 1 189 ₽.
Вердикт: CPS < вклад до рекламы → реклама окупается. Отношение вклад / CPS — запас.
⚠ Что в этот вклад не входит: общемагазинные операции Ozon, не привязанные к отправлению (сбор первых отзывов, досрочная выплата, кросс-докинг, подписка Premium, страхование — по shop 4 это ≈106 ₽ на проданную единицу), и всё вне Ozon (ФФ, доставка до склада, ФОТ). Для строгой оценки вычитать; на вердикт по ELS004 Black это не влияло (запас 1.8× → 1.7×).
6.5. Две группы метрик: опережающие и основные
Разделять всегда — они ломаются в разных местах и лечатся разным.
| Группа | Метрики | Про что |
|---|---|---|
| Опережающие | показы РК, клики, CTR, клик→корзина, корзины, цена корзины (CPL) | работа аукциона и креатива: сколько и по какой цене реклама приводит людей |
| Основные | все заказы, корзина→заказ, продажи после возвратов, CPS, ДРР | доходит ли приведённый интерес до денег |
Диагностическое правило. Опережающие ровные, а основные едут вниз — проблема не в рекламе. Между корзиной и заказом стоят: наличие размера в кластере покупателя, срок доставки, цена, конкуренты. Поднимать ставку в такой ситуации — тушить не тот пожар.
Безубыточная цена корзины = вклад до рекламы × конверсия корзина→продажа. Она даёт
опережающий порог, который виден раньше, чем испортится CPS.
6.6. Что данными не решается — только эксперимент
Наблюдением нельзя получить: вклад рекламы отдельно от сезона; эластичность по цене; правильное деление бюджета между цветами/размерами.
Схема (одна на все такие вопросы). Плечо — одна пара цвет/размер, контроль — соседний цвет той же модели, окно — две недели, метрики — все корзины, корзина→заказ и CPS обоих плеч. Ставится сдвиг ровно одного параметра: бюджет ±30–50% либо цена кабинета ±10–15%.
🔴 Тест ставится на товаре в фазе роста, а не на уходящем сезоне. Контроль снимает тренд, но падающие объёмы убивают чувствительность. Ориентиры по чувствительности (80% мощности, 5% уровень, две недели на плечо, конверсия корзина→заказ ~30%):
| Объём на плечо | Ловится разница в заказах | Ловится разница в конверсии |
|---|---|---|
| 240 заказов / 900 корзин | ~26% относительных | ~6 п.п. |
| 480 заказов / 1 800 корзин | ~18% | ~4 п.п. |
| 100 заказов / 400 корзин | ~40% | ~9 п.п. |
ELS004 Black в июле–августе 2026 давал ~120 заказов и ~460 корзин в неделю на цвет — то есть на границе, где тест ещё имел смысл. К концу сезона объёмы падают, и окно закрывается.
Практический вывод: тест закладывается в план запуска нового товара, а не изобретается
задним числом. Статус запусков — ../Strategy-Operations/products/<товар>/launch.yaml
(скилл launch-operations); там же держать окно теста, плечо и контроль.
6.6a. Цена как фактор: проверено, зависимости не найдено
Разбор ELS004 Black M, Siluetta Ozon, 05.07–22.08.2026, 41 день с полными данными
(ozon_buyer_price_daily + воронка + постинги).
Корреляции цены покупателя со всей воронкой около нуля и со знаком «дороже → чуть больше корзин»: корзины +0.18, заказы +0.16, корзина→заказ +0.10. Внутри уровней остатков (главный конфаундер снят) — +0.08 / +0.05 / +0.19. Терцили по цене плоские: корзин в день 24.9 / 27.1 / 27.9, конверсия 29 / 33 / 28%.
⚠ Корреляция цены кабинета с корзинами (−0.59) — артефакт времени, а не эффект: 5 690 стояла почти весь июль, 5 100/5 490 — в августе. Уровней всего четыре и они не пересекаются во времени. Не читать как эластичность.
Квази-эксперимент 13.08 (цену black подняли 5 100 → 5 490, у brown и grafit не трогали):
| Товар | 29.07–05.08 | 06–12.08 | 13–18.08 | Цену меняли |
|---|---|---|---|---|
| black-M | 37% | 26% | 20% | −10%, затем +7.6% |
| black-S | 30% | 34% | 30% | −10%, затем +7.6% |
| brown-M | 27% | 35% | 32% | нет |
| grafit-M | 33% | 34% | 35% | нет |
Направление не воспроизводится: на снижении цены black-M ухудшился, а black-S улучшился; grafit при неизменной цене не просел вовсе. Второй эпизод — 14–15.07, самая низкая цена покупателя за период (−15%): заказы 6 и 9 при 10 накануне и 7 назавтра, конверсия 24 и 31% против 43 и 37%. Скидка подъёма не дала.
Почему цену покупателя нельзя брать за рычаг. За период цена кабинета имела размах 11.6%
(5 100–5 690), а цена покупателя — 34.1% (2 408–3 229), при corr(кабинет, покупатель) = +0.13.
При неизменной цене кабинета 5 690 витринная цена гуляла на 30.9% (σ = 6.6%), день к дню
в среднем на 3.6%, максимум на 14.7%. Доля покупателя от кабинета — 43–59%. Ozon двигает
витрину своей скидкой сильнее и почти независимо от нас.
⚠ Это не доказательство отсутствия эластичности: свою цену за 41 день подвинули всего на 11.6%, на фоне падающего сезона и рушащейся географии остатков. Такой дизайн не поймал бы и эффект средней силы. Мерить эластичность — только намеренным тестом по §6.6, на растущем товаре.
Что из этого следует для разбора: версию «просели заказы, потому что подняли цену» проверять этими данными можно и обычно она закрывается. Заявлять «цена не влияет» вообще — нельзя.
6.7. Дефицит рекламируемого размера бьёт по всей карточке
Карточка одна: реклама размера M генерирует показы и корзины всех размеров. Поэтому проверять остатки надо по тому размеру, на который льют бюджет, и не в штуках, а в числе складов с остатком — товар может быть «в наличии» суммарно, но отсутствовать в кластере покупателя, и тогда корзина не превращается в заказ из-за срока доставки.
Кейс ELS004 Black, август 2026 (M — 2/3 бюджета):
| Размер | Неделя | Расход | Корзины | Заказы | Корз.→заказ | Складов | Остаток |
|---|---|---|---|---|---|---|---|
| M | 26.07 | 12 020 | 145 | 52 | 36% | 22 | 121 |
| M | 09.08 | 11 617 | 230 | 50 | 22% | 14 | 56 |
| M | 16.08 | 12 523 | 195 | 47 | 24% | 6–8 | 24–33 |
| S | 16.08 | 4 279 | 170 | 53 | 31% | 13 | 37 |
| L | 16.08 | 0 | 96 | 16 | 17% | 2 | 2 |
Цена корзины при этом держалась 50–70 ₽ без тренда — реклама отработала честно. L показывает второй эффект: реклама выключена с 26.07, а корзины идут (74–111/нед) — их приносит карточка целиком; заказы упирались в остаток 1–6 шт.
Следствия: рекламировать размер с околонулевым остатком — слив; чинить надо поставку по
географии (ozon/supply-playbook.md), а не ставку.
6.8. Пороги мониторинга
| Метрика | Порог | Что означает |
|---|---|---|
| CTR | < 5.5% две недели подряд | аукцион отдаёт худшую аудиторию — фото/ставка |
| Цена корзины (CPL) | > 80% безубыточной | подбирается к пределу |
| Корзина→заказ | < 28% | проблема ниже воронки, реклама ни при чём |
| Складов с остатком по рекламируемому размеру | < 10 | чинить поставку, не ставку |
Источники цен для проверки версии «дело в цене» — ozon_buyer_price_daily
(ozon/buyer-price-playbook.md). ⚠ Покрытие дырявое: по shop 4 за 05.07–22.08 есть 41 день
из 49 (нет 25–28.07 и 19–26.08); по shop 2 прогоны с 23.08.2026 висят в статусе running с
rows_count = 0. Считать только по дням, где снимок есть.
Пороги калиброваны на ELS004 Black (комбинезон, средний чек ~5 400 ₽ цены продавца). Для другой категории пересчитывать, а не переносить.
6.9. Зависимость от подписки Premium
Блок опережающих метрик по карточке (все корзины, все показы, конверсия корзина→заказ) идёт
из ozon_sku_traffic_daily и доступен только при активной платной аналитике. С 22.08.2026
на shop 4 тариф отключён, и Ozon отдаёт лишь 8 метрик вместо ~60 (revenue, orderedUnits,
soldRevenue и динамика) — это не поломка сбора. Без подписки знаменатель «все корзины»
пропадает и остаются только рекламные счётчики плюс постфактумный CPS.
7. Чек-лист разбора рекламы Ozon
Данные
- ☐ Кампании и ad-stats доингещены за окно? Креды Performance резолвятся (perf_cred_path Linux)?
- ☐ В
ozon_ad_metrics_dailyесть ненулевойmoneyза окно? Еслиorders=0приmoney>0— смотреть §5 «Колонки CSV переименованы» (парсер не нашёлПродано товаров). - ☐ Свод расхода по
placement/campaign_idснят — видно, где деньги (CPO vs поиск vs поиск+реком)? - ☐ Реклама сверена с finance (0.1%)?
- ☐ Дни с неполными данными исключены, а не занулены? (пропуски снимков аналитики, отключённый тариф §6.9, оборванный день по расходу). Все метрики считаются на одном наборе дней, и число дней указано в выводе.
Оценка (§6)
- ☐ Недели полные и выровнены по дню недели; недозревшие когорты помечены?
- ☐ Знаменатель — ВСЕ корзины и ВСЕ заказы; разложения на «рекламные/органические» нет?
- ☐ Продажи считаются после возвратов (
deliveredминус финансовые возвраты), а не поdelivered? - ☐ Вклад до рекламы пересчитан по свежему finance-окну, а не взят из старого разбора?
- ☐ Вердикт дан по
CPS < вклад, а не по CPO/CPA из кабинета? - ☐ Опережающие и основные метрики разведены; если основные едут при ровных опережающих — диагноз ищется ниже воронки, а не в ставке?
- ☐ Проверены остатки рекламируемого размера в разрезе числа складов (§6.7)?
- ☐ Версия «дело в цене» проверена по
ozon_buyer_price_daily— и цена кабинета, и цена покупателя, только на днях со снимком (§6.6a)?
Вывод пользователю
- ☐ Названо, какие решения данные поддерживают, а какие требуют эксперимента (§6.6)?
- ☐ Если предлагается тест — товар в фазе роста и объёмов хватает на чувствительность, а не «поставим на уходящем сезоне»?
- ☐ Если копаем поиск — фразы тянем только по
TOP_PROMOTION; помним: CTR ≠ конверсия, минус-слов на Ozon нет?
8. Реальный кейс (HZ030-Brown, Мой Ozon, июнь 2026)
Артикул убыточен по юните; реклама 25 263 ₽ = 60% бюджета магазина. Разложение: CPO (ДРР 10%, управляемо) ≈ 8.5к + CPC внутри CPO ≈ 8.6к + CPC «Поиск» 6.4к + CPC «поиск+полки» 1.7к. ~16.7к из 25.3к = CPC на общих фразах («комбинезон женский летний», «Каталог» — CTR 1.7–2.7%) с неуправляемым ДРР — вот это утечка, а не CPO-10%. Решение: по убыточным SKU оставлять CPO (ДРР-cap), а поисковый CPC резать. См. [[ads-playbook]] §3 (фразы), §2 (разрез по типам).
9. Реальный кейс (ELS004 Black, Siluetta Ozon, июль–август 2026)
Разбор, на котором построен §6. Окно 05.07–22.08, недели полные, только дни с полными данными (39 из 42 — пропуски снимков 25–27.07 и 20.08).
Опережающие держатся ровно: цена корзины 50–70 ₽, клик→корзина 3.7–4.1% без тренда, CPC ~2.3 ₽. Реклама отработала предсказуемо весь период.
Основные поехали:
| Неделя | Расход | Корз. всего | Заказы | Корз.→заказ | CPO | Продажи | CPS | ДРР |
|---|---|---|---|---|---|---|---|---|
| 05.07–11.07 | 13 529 | 322 | 98 | 30% | 138 | 23 | 588 | 10.3% |
| 19.07–25.07 | 13 016 | 323 | 105 | 33% | 124 | 37 | 352 | 6.2% |
| 02.08–08.08 | 16 910 | 519 | 171 | 33% | 99 | 50 | 338 | 6.4% |
| 09.08–15.08 ⚠ | 16 753 | 502 | 129 | 26% | 130 | 43 | 390 | 7.5% |
| 16.08–22.08 ⚠ | 16 802 | 461 | 116 | 25% | 145 | 29 | 579 | 10.7% |
Итого за зрелое окно: расход 90 798 ₽, корзины 1 516 РК из 2 402 всего (доля РК 63%), заказы 731, продажи после возвратов 222 шт на 1 207 365 ₽. CPS 408 ₽ при вкладе 1 189 ₽ — запас 2.9×. Безубыточная цена корзины 110 ₽ против факта 60 ₽.
Диагноз: опережающие ровные, основные вниз → смотреть ниже воронки. Нашлось в §6.7: у размера M (2/3 бюджета) число складов с остатком упало 22 → 6, и конверсия корзина→заказ там просела 36% → 22%. Цена покупателя в тот же период снизилась (3 093 → 2 702 ₽), то есть версия «подняли цену» отпадает.
Принятые решения: рекламу не трогать (запас 2.9× и r=0.94 расход↔заказы); приоритет — поставка M по географии; L включать только после пополнения (с 26.07 по 27.08 расход на него был 0 при живых 74–111 корзинах в неделю и остатке 1–6 шт).