Я уже рассказывал вам о том, что бид-менеджеры не эффективны, но и я сам не осознавал насколько!  Также в статье рассказано об оптимальной ставке в новом аукционе и будет экскурс в R.

Большинство моих читателей уже знают почему бидменеджеры не работают и об оптимальных ставках в Директе. Поэтому, по ходу пьесы, я расскажу, как пользоваться языком программирования R (это просто и полезно).

Бидменеджеры

Есть два типа программ оптимизирующих ставки в контекстной рекламе:

  • Бидменеджеры. Они основываются на стоимости позиций из интерфейса Директа (хотя в самой справке Директа написано, что эти цифры ничего не значат).
  • Оптимизаторы конверсий. Они ориентируются на конверсию, средний чек и прочее.

Сейчас после смены алгоритма аукциона в Яндекс.Директ, пиарщики бидменеджеров хватаются за последнюю соломинку: «между блоками не действует VCG, поэтому,  бидменеджеры работают». Разрушим этот миф.

Почему мы должны доказывать обратное? Производитель лекарств проводит их клинические испытания и доказывает, что лекарство работает.

Бидменеджеров штук 20, и они уже существуют лет 10.  Почему никто не показал ни одного вменяемого кейса? Для сравнения посмотрим на оптимизаторы конверсий. У К50 на сайте 5 кейсов.

Сейчас в комментах меня начнут спрашивать: «Почему К50 не сделает кейс который доказывает что биддеры не работают?»

К50 доказательство этого факта ничего не принесет. К50:Оптимизатор для очень больших клиентов (минимальная абонплата 50.000 рублей), а бид-менеджеры использует мелкие клиенты. Единственный оптимизатор конверсии для мелких клиентов это мой бесплатный Bid-Expert.

R

R это довольно простой язык программирования. Он предназначен для исследования данных (big-data) или для моделирования процессов связанных со случайными величинами.

Лет через пять знания R будет обязательным требованием для Senior-PPC, поскольку контекстная реклама и интернет-маркетинг во многом объясняются теорией вероятности. Более того, для исследований больших данных R очень полезен.

Первым делом нужно установить R и R-Studio. Это просто. Затем запускаем R-Studio и пишем код. Чтобы код выполнить нужно его выделить и нажать ctrl+enter.

Модель

Уже никто не спорит, что внутри блоков действует VCG, в котором оптимальная ставка это ценность клика. Нужно доказать, что и между блоками биддеры не работают. Для этого напишем простую модель на R, которая отражает переходы между блоками.

Допустим у нас есть ключевое слово. Для простоты предположим, что у нас стратегия в блоке «по минимальной цене» и почти все клики из спецразмещения или гарантии (динамику не учитываем).

Допустим CTR в гарантии 1%, а в СР —10%. Пускай мы торгуем чайниками, покупаем за 200$, а продаем за 300$, конверсии = 3%. Максимальная сумма которую мы можем заплатить за клик (ценность клика) = 3%*(300$-200$)=3$.

srCTR      = 0.1    # CTR спеца 10%
garCTR     = 0.01   # CTR гарантии 1%
clickValue = 3      # Ценность клика = 3$

У ключевого слова есть показы. В одной из прошлых статей я показал, что стоимость входа в блоки у разных показов разная.

nImpressions = 100500 # число показов у ключа
srPrice      = runif(nImpressions, 1,   6) #Стоимость входа в спец
garPrice     = runif(nImpressions, 0.1, 4)#Стоимость входа в гарантию

Этот код генерирует 100500 показов у ключа. Почему так много? Поскольку чем их больше тем выше точность. Тем меньшее влияние случайности на наши результаты. Но вы можете убедится, что и на меньшем числе показов наша модель даст аналогичные результаты.

Функция runif(nImpressions, 1, 6), генерирует 100500 случайных чисел от 1 до 6. Мы в будущем поменяем диапазоны и заменим  функции распределения, чтобы убедиться, что на результат не влияют входящие данные.

Массивы

Преимущество R перед другими языками программирования в том, что там очень простая работа с массивами. Мы можем производить большинство операций без использования циклов.

Вместо такого кода:

for(i in 1:nImpressions){
   srPrice[i]=srPrice[i]*2
}

Можно написать просто:

srPrice=srPrice * 2

Есть функции которые считают сумму (sum()) и среднее (mean()) по массиву.

Сравнение

Это один из самых крутых приемов в R. Если вы освоите его, то большинство вещей будите делать вдвое быстрее.

Также можно сравнивать элементы массивов srPrice>garPrice вернет массив из единиц (истина) и нулей (ложь). Поэтому долю показов в спеце при ставке в 2 можно вычислить так: mean(srPrice<2).

Допустим нам нужно вычислить средний CPC в СР при ставке в 2$. Мы попадаем в гарантию во всех показах у которых стоимость входа меньше 2. Таких показов sum(srPrice<2).

Выражение (srPrice<2)*srPrice, равно 1*srPrice=srPrice для всех показов у которых srPrice<2, а для всех остальных 0*srPrice=0.

Нам нужно найти среднее по всем ненулевым элементам этого массива. Берем сумму этого массива и делим на число ненулевых элементов:

optbid6

Так образом мы вычислим средний CPC в спеце при ставке 2.

Число кликов

Теперь вычислим сколько кликов мы получим при ставке bid.

Нужно вычислить число показов в СР и умножить на CTR в СР.

srCTR*sum(bid>srPrice)

В гарантию мы попадаем когда выполняются одновременно 2 условия. Первое мы не попадаем в СР. Второе стоимость входа в гарантию меньше . Поэтому формула такая.

garCTR*sum((bid<srPrice)*(bid>garPrice))

Просуммируем клики с двух блоков:

clicks  = srCTR*sum(bid>srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice))

Расход и прибыль

Расход аналогично считается

expense  = srCTR*sum((bid>srPrice)*srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice) *garPrice)

Прибыль суммарная ценность купленных кликов минус расход

profit = clicks * clickValue - expense

График

Давайте посмотрим как меняется наша прибыль в зависимости от ставки от 0.01 до 5:

srCTR      = 0.1    # CTR спеца
garCTR     = 0.01   # CTR гарантии
clickValue = 3      # Ценность клика

nImpressions = 100500 # число показов у ключа
srPrice      = runif(nImpressions, 1,   6)
garPrice     = runif(nImpressions, 0.1, 4)

bids = c() # массив со ставками
profits = c() # массив с прибылями 

max = 0 # макс. прибыль
optBid = 0 # оптимальная ставка
 
for(i in 1:500){
 bid = i/100 #ставка
 bids[i] = bid

 clicks  = srCTR*sum(bid>srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice))

 expense  = srCTR*sum((bid>srPrice)*srPrice) + garCTR*sum((bid<srPrice)*(bid>garPrice) *garPrice)
 
 profit = clicks * clickValue - expense

 profits[i]=profit
 if(profit>max){
    max    = profit
    optBid = bid 
 }
}
plot(x=bids,y=profits)

optbid

Чтобы вывести на экран оптимальную ставку нужно просто написать optBid. Получится 2.9.

Берем формулу оптимальной ставки из этой статьи и получаем формулу

 k = mean(srCTR/garCTR)
((k-1)*clickValue + mean(garPrice))/k

И получаем 2.905.  Может это конечно, совпадение. Поменяем, начальные условия.

srPrice      = runif(nImpressions, 2,   5)
garPrice     = runif(nImpressions, 0.5, 2.5)

Получаем совсем другой график:

optbid2

Но формула снова верна: 2.85 против 2.8502 по формуле.

Гамма распределение

У нас график получился некрасивым (не гладким). Это поскольку мы использовали равномерное распределение, которое не гладкое и практически не встречается в природе.

Заменим его на другое распределение. Можно на нормальное (но оно может выдавать отрицательные числа). Поэтому возьмем гамму (оно генерирует положительные числа):

srPrice      = rgamma(nImpressions, 7,   2) #среднее 7/2 = 3.5
garPrice     = rgamma(nImpressions, 1,   2)
#среднее 1/2 = 0.5

optbid3

Получаем 2.8 против 2.799821 по формуле.

Промежуточные выводы

  • Формула работает (если кто-то все-еще сомневается — поиграйтесь в с начальными условиями)
  • Оптимальная ставка не зависит от стоимости входа в СР. Это видно по формуле.  Ни внутри блоков (там VCG), ни снаружи.
  • Программы которые на нее ориентируются работают в минус.
  • Все эти выводы легко опровергнуть, просто подобрав цифры, где формула не верна (см. в конце статьи код с учетом, того что гарантия может быть недоступной).

Внутри блоков оптимальная ставка clickValue (это доказанное свойство VCG аукциона) между блоков ((k-1)*clickValue + mean(garPrice))/k. Итого оптимальная ставка будет где-то между ними.

При k=10 и малом стоимости входа между блоками оптимальная ставка около 0.9 * clickValue. Итоговую ставку можно оценить как 0.95 * clickValue.

Однако, в большинстве случаев нужно вычитать НДС и разделить на 1.18. Получим  0.805 * clickValue.

Эффективность

Теперь оценим на эффективность стратегий в процентах. Максимальную прибыль возьмем за 100% эффективности.  Например, 50% это значит, что прибыль вдвое ниже чем у максимальной стратегии.

Алгоритм 0: искренняя ставка

Самый простой алгоритм. Устанавливаем ставку равную ценности клика.

Поскольку мы уже посчитали прибыль для разный ставок и записали их в массив profits, то можно «вспомнить» прибыль, которая была при ставке равной ценности клика:

profit_a0 = profits[round(clickValue*100)]

Считаем эффективность mean(profit_a0)/max

Получаем 98.5%

Алгоритм 1: по формуле

В формуле есть средняя стоимость входа в гарантию, которая нам неизвестна. Нам из интерфейса Директа известна только стоимость входа в блок при запросе точно соответствующем ключевому слову, в самом дорогом регионе. Грубо говоря, стоимость в случайном показе.

Алгоритм: берем вместо mean(garPrice) циферку «вход в гарантию» из интерфейса Директа.

Мы можем подставить в формулу вместо mean(garPrice) любое из этих чисел garPrice[1], garPrice[2], … Посчитав его имитацией циферки в интерфейсе Яндекса.

bid_a1 = ((k-1)*clickValue + garPrice)/k
profit_a1 = profits[round(bid_a1*100)]
mean(profit_a1)/max

Мы получим 100500 возможных ставок сделанных по этому алгоритму в зависимости от того, данные какого показа были в интерфейсе Директа.  И 100500 возможных значений прибыли. Потом считаем среднюю эффективность.

Получим 99.8%

Алгоритм 2: бидменеджер

Типовой алгоритм бидменеджера:

  1. Если цена_входа_в_СР+0.01<3, то устанавливаем ставку цена_входа_в_СР+0.01
  2. В противном случае, если цена_входа_в_гарантию+0.01<3, то устанавливаем ставку цена_входа_в_гарантию+0.01
  3. В противном случае устанавливаем 0.01.
bid_a2    = ((srPrice + 0.01 < clickValue) * (srPrice) 
           + (srPrice + 0.01 > clickValue) * (garPrice + 0.01 < clickValue) * (garPrice)
           + 0.01)
profit_a2 = profits[round(bid_a2*100)]
mean(profit_a2)/max

Получим 55%. Т.е. прибыли вдвое меньше! Причем это даже без учета того, что внутри блоков происходит.

Алгоритм 2.2: бидменеджер 2

Меняем действие в третьем случае. Вместо минимальной ставки (0.01) ставим ценность клика (3).

  • Если цена_входа_в_СР+0.01<3, то устанавливаем ставку цена_входа_в_СР+0.01
  • В противном случае, если цена_входа_в_гарантию+0.01<3, то устанавливаем ставку цена_входа_в_гарантию+0.01
  • В противном случае устанавливаем 3.
bid_a22    = ((srPrice + 0.01 < clickValue) * (srPrice + 0.01) 
             +(srPrice + 0.01 > clickValue) * (garPrice + 0.01 < clickValue) * (garPrice + 0.01)
             +(srPrice + 0.01 > clickValue) * (garPrice + 0.01 > clickValue) * clickValue)
profit_a22 = profits[round(bid_a22*100)]
mean(profit_a22)/max

Получим 56%.

Исходный код

Вы можете скачать исходный код, в нем много комментариев, защита от некоторых ситуаций (когда гарантия недоступна например), а также алгоритмы реализованы в 2 видах (в том числе и в  цикле).

Выводы

Эти входящие данные я специально не подбирал — первое на чем я проверил. При разных входящих данных, потери из-за бидменеджера разные. Но всегда бидменеджер это худший алгоритм, а первый алгоритм (формула) — лучший.

  • Бидменеджеры это куча усилий, чтобы вдвое сократить свою прибыль. Проще просто вырубить показы по четным часам или не отвечать на половину звонков
  • Они проигрывают даже искренней ставке (тупо установить максимум того, что вы готовы потратить)
  • Все это касается и ручной перебивки

Однако, не все сервисы одинаковые, есть оптимизаторы конверсии. Они  работают совершенно по другому принципы. Оптимизаторы , благодаря математике, прогнозируют  вероятность конверсии (и средний чек) и ориентируясь на эти данные делают ставки.

Например, мой бесплатный Bid-Expert.