InquirerDespite economic problems, Undas flower vendors strive to survivePunchUganda begins troop withdrawal from SomaliaThe Jerusalem PostRapper Ja Rule comes to Tel Aviv, speaks on legacy, aging, and Tupac-Biggie row - interviewZDF heuteAktuelle Pressemitteilungen des ZDF20 MinutenWie viel weisst du noch aus deiner Kindheit? Erzähls uns!The South AfricanHow the Springboks can lose World Rugby No 1 ranking this weekendNHK 社会鳴子峡近くの5人けが事故 「アクセルとブレーキ踏み間違えた」VanguardCatholic Priest leads congregation in song for Obi, KwankwasoPremium TimesUniversity cancels final-year students’ exam over ‘wild jubilation’GMA News2 sakay ng habal-habal, patay matapos mabangga at madaganan ng dump truck sa Negros OrientalEl ComercioResultados ONPE en Villa María del Triunfo al 99.676 %: sigue aquí el conteo oficial de las Elecciones Regionales y Municipales 2026GhaflaDCI Arrests Man Who Allegedly Spent KSh43,320 Mistakenly Sent to His Phone
The Daily Newsstand · Free, Always
Monday, November 2, 2026

Считаю юнит-экономику в коде: где расходятся формулы калькуляторов

Translate

Калькулятор юнит экономики для маркетплейса я собирался написать за вечер: цена минус расходы, что тут писать. Расчет юнит экономики оказался простым ровно до первой попытки сверить его с чужими. Я взял пять разборов с примерами из первой страницы выдачи, перевёл каждый пример в код и пересчитал. Четыре сошлись, один нет. А когда дошёл до невыкупа, выяснилось, что в той же выдаче лежат четыре разные формулы, и на одних и тех же цифрах они дают от 155 до 383 рублей на штуку.

Все цифры ниже условные: это учебные примеры авторов разборов или числа, которые я подставил для сравнения. Прогнозом дохода они не являются.

Калькулятор юнит экономики на сорок строк

Сначала модель. Мне нужно было, чтобы в одном месте было видно, что считается долей цены, а что рублями на штуку. В таблицах это обычно перемешано, и половина споров потом из-за этого.

```python @dataclass class Unit: price: float # цена, от которой площадка берёт проценты cost: float # себестоимость до склада площадки, руб. fee: float = 0.0 # комиссия, доля цены acquiring: float = 0.0 # эквайринг, доля цены ads: float = 0.0 # реклама, доля цены tax: float = 0.06 # УСН "доходы", доля полной цены rub: dict = field(default_factory=dict) # рублёвые строки на штуку

def profit(u: Unit) -> float: shares = u.fee + u.acquiring + u.ads + u.tax return u.price * (1 - shares) - u.cost - sum(u.rub.values()) ```

Рублёвые строки лежат словарём, потому что у каждого автора свой набор: у кого-то хранение отдельно, у кого-то брак, у кого-то платная приёмка. Вписывать их полями значило бы плодить нули.

Дальше каждый пример из статей я перенёс в словарь с числом, которое указано на странице, и прогнал:

`python for name, ex in EXAMPLES.items(): got = round(profit(ex["unit"]), 2) diff = round(ex["claimed"] - got, 2) mark = "ok" if abs(diff) < 1 else "НЕ СХОДИТСЯ" print(f"{name:30} страница {ex['claimed']:>8} пересчёт {got:>8} {mark}") `

` uniseller, футболка WB страница 62 пересчёт 310.3 НЕ СХОДИТСЯ uniseller, светильник WB страница 772.5 пересчёт 772.5 ok uniseller, термокружка Ozon страница 448 пересчёт 448.0 ok cleverence, товар за 2000 страница 540 пересчёт 540.0 ok МойСклад, колье WB страница 217.82 пересчёт 217.82 ok `

С футболкой я первым делом искал ошибку у себя. Цена 1 870, себестоимость 700, комиссия 15%, логистика 250, хранение 30, реклама 10%, налог 6%. На странице итог 62 рубля и 4% маржи. Из этих же строк получается 310 рублей, а 311, если брать округлённые суммы, которые там же и написаны. Разница почти 250 рублей, и ни одной строки такого размера в списке нет. Возможно, автор держал в голове возвраты или платную приёмку и не дописал их. Но читатель видит только список и итог, и сложить их у него не получится.

Колье сошлось не сразу. Я вбил логистику 28,5 рубля, как она указана, и недосчитался полутора рублей. Потом заметил, что рядом стоит выкуп 95%: в таблице МоегоСклада логистика делится на долю выкупа, 28,5 / 0,95 = 30. После этого совпало до копейки. Этот случай и навёл меня на следующий раздел.

Расчет юнит экономики ломается на невыкупе

На маркетплейсе часть заказов не выкупают. Доставку до пункта выдачи продавец оплатил, товар едет обратно, и это тоже стоит денег. Все разборы это признают, но считают по-разному. Я нашёл четыре варианта:

  • светильник у uniseller: возвраты закладываются как 5% от цены, отдельной

строкой;

  • cleverence: стоимость возврата = обратная доставка × (1 - p) / p, где p -

доля выкупа;

  • МойСклад: логистика делится на p;

  • selsup: все расходы на невыкупы за период делятся на число выкупленных

единиц.

Последний вариант мне показался единственным, который описывает то, что происходит на самом деле. Если отправить N заказов, выкупят pN. Доставку туда оплачиваем за все N, дорогу обратно за (1 - p)N. Делим на pN и получаем расходы на одну продажу. Остальные формулы теряют одну из дорог либо вообще не зависят от выкупа.

`python def logistics_per_sale(method, L, R, p, price=0.0, reserve=0.05): """L - доставка до покупателя, R - обратная дорога, p - доля выкупа.""" if method == "доля от цены": return L + reserve price if method == "только обратная": return L + R (1 - p) / p if method == "только прямая": return L / p if method == "обе дороги": return (L + (1 - p) * R) / p raise ValueError(method) `

Первый прогон я сделал с доставкой 80 рублей в обе стороны и обрадовался: вторая и третья формулы дали одно и то же. 84,2 при выкупе 95%, 114,3 при 70%, 266,7 при 30%. Решил, что авторы просто записали одно и то же разными словами. Потом расписал на бумаге: L + R(1 - p)/p и L/p совпадают только при L = R. Совпадение было в моих входных данных, а не в формулах. С обратной дорогой 50 рублей они расходятся:

Расчет юнит экономики при выкупе 30%: схема, сколько логистики приходится на одну выкупленную штуку по четырём формулам из выдачи - 155, 197, 267 и 383 рубля при цене 1 500, доставке 80 и обратной дороге 50 рублей

Расчет юнит экономики при выкупе 30%: схема, сколько логистики приходится на одну выкупленную штуку по четырём формулам из выдачи - 155, 197, 267 и 383 рубля при цене 1 500, доставке 80 и обратной дороге 50 рублей

Выкуп

5% от цены

только обратная

только прямая

обе дороги

95%

155,0

82,6

84,2

86,8

70%

155,0

101,4

114,3

135,7

30%

155,0

196,7

266,7

383,3

У резерва «5% от цены» поведение самое коварное. При высоком выкупе он завышает расходы почти вдвое, и товар кажется хуже, чем есть. При низком занижает в два с половиной раза, и в плюс попадает то, что на деле в минусе. Одежда с выкупом около трети как раз из второй группы, и для неё разница 228 рублей на каждой проданной штуке. У колье из примера МоегоСклада вся прибыль с продажи 218 рублей, то есть одна неудачная формула съела бы её целиком.

Оговорка, без которой картина нечестная: я считаю, что невыкупленная вещь возвращается целой и уходит следующему покупателю. Если часть приходит мятой или без бирок, к «обеим дорогам» добавляется ещё и доля себестоимости. Ни один из разборов этого в формулу не вносит, только упоминает словами.

Налог: от чего шесть процентов

Здесь разборы как раз сходятся: УСН «доходы» считают от полной цены, по которой купил покупатель. Это совпадает с позицией ФНС (письмо от 08.05.2024 № СД-4-3/5416@): доход продавца - вся сумма, а комиссия площадки - его расход, а не уменьшение выручки.

Я проверил, что будет, если взять налог от выплаты, то есть от суммы, которую площадка перечислила. Именно так его хочется считать, когда смотришь в выписку. На термокружке за 1 500 рублей 90 рублей превращаются в 72,78. Семнадцать рублей на штуку выглядят мелочью, пока не умножишь их на годовой оборот и не получишь доначисление.

МойСклад в своей таблице ставит 7%: 6% налога и 1% взносов. Это честная поправка, но с нюансом: 1% платится с дохода сверх 300 тысяч в год, так что на первой партии ставка ближе к 6%, а дальше к 7%. В коде у меня tax отдельным параметром, и я подставляю то, что подходит к конкретному году.

Обратная задача: какая нужна цена

Обычный калькулятор отвечает на вопрос «сколько останется». На практике чаще нужен обратный: какую цену ставить, чтобы осталось хотя бы 300 рублей. Наивный ход - прибавить к расходам 300 и всё. Не работает, потому что комиссия, реклама и налог сами зависят от цены. Цена оказывается в обеих частях уравнения, и её надо вынести:

`python def min_price(cost, rub_total, shares, want=0.0): # проценты берутся от самой цены, поэтому цена в знаменателе return (cost + rub_total + want) / (1 - shares) `

Такую же формулу приводит selsup. Я проверил её прямым расчётом: для термокружки (себестоимость 500, рублёвых строк 165, долей 25,8%) выходит 1 300,54 рубля, и profit() на этой цене возвращает ровно 300,00. Такой тест я теперь держу рядом с любой правкой модели: обратная функция и прямая должны сходиться, иначе одна из них врёт.

Отдельный вопрос, от какой цены вообще считать доли. В разборе selsup со ссылкой на справку Яндекс Маркета сказано, что часть услуг площадка считает от «вашей цены», а не от того, что заплатил покупатель после скидки площадки или баллов. Если в поле price подставить витринную цену, комиссия и плата за перевод денег окажутся ниже настоящих. Поэтому в модели у поля такой длинный комментарий.

Итог и что я не посчитал

Из пяти примеров четыре пересчитываются один в один, пятый расходится на 248 рублей без объяснения. Формулы невыкупа в выдаче не одна, а четыре, и при выкупе около 30% разница между крайними больше 200 рублей на штуку. Если калькулятор не показывает, какую из них он применяет, его ответу нельзя верить сильнее, чем вашей собственной догадке.

Чего здесь нет. Тарифы площадок я не подставлял: они меняются несколько раз в год и зависят от категории, склада и габаритов, поэтому все числа условные. Хранение я считал рублём на штуку, хотя на деле оно зависит от оборачиваемости, и это отдельная работа. Брак и порчу при возврате не моделировал. Рекламу брал долей цены, а у тех, кто платит за клики, она тоже делится на выкуп. Если кто-то видел разбор, где невыкуп считается иначе, чем в этих четырёх вариантах, напишите в комментариях: добавлю пятую ветку и прогоню на тех же цифрах.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.