Ви прокидаєтеся вранці й бачите, що ваш товар на Ozon полетів на 100 сторінку видачі — ціну збили конкуренти за ніч. Handmade демпінг веде до нульової маржі та нерозпроданого стоку. Потрібна автоматизація: бот, який моніторить ціни конкурентів, застосовує гнучкі стратегії та публікує корекції через API маркетплейсів. Нижче — як ми це робимо.
Коли ручне управління не рятує: типові проблеми продавця
Конкуренти змінюють ціни кілька разів на день — встежити самому неможливо: або впустиш прибуток, або продаєш у мінус. Наш бот реагує в 3 рази швидше, ніж ручне управління, скорочуючи час відгуку до 30 хвилин. Багато стратегій в Excel не працюють, тому що час відгуку вимірюється хвилинами, а не годинами. Немає захисту від цінових війн — два селери ганяють ціну до нуля, а третій викидає товар за собівартістю. Складно тестувати гіпотези — правило «знизити на 5%, якщо конкурент знизив» без репрайсинг-бота перетворюється на аналоговий ад.
Правильно налаштований repricing — це стабільне зростання конверсії при збереженні маржинальності. Помилка в одному коефіцієнті — і ви в мінусі. Тому бот повинен уміти: дотримуватися маржинального флору, аналізувати позицію в Buy Box, примусово призупинятися при підозрі на цінову війну.
Як репрайсинг-бот ухвалює рішення?
Стек звичний: PHP 8.3 + Laravel 11 для бекенду, PostgreSQL для зберігання правил, логів та статистики. Двигун репрайсингу — набір класів із чіткими відповідальностями: правило (RepricingRule), двигун (RepricingEngine), детектор цінової війни (PriceWarDetector) та публікатор (OzonPricePublisher / WbPricePublisher).
Схема даних: правила репрайсингу
CREATE TABLE repricing_rules (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
marketplace VARCHAR(50) NOT NULL, -- 'ozon', 'wildberries', 'yandex_market'
scope_type VARCHAR(20) NOT NULL, -- 'global', 'category', 'product'
scope_id BIGINT,
strategy VARCHAR(30) NOT NULL, -- 'min_price', 'buy_box', 'rule_based'
min_price_mode VARCHAR(20) DEFAULT 'margin_floor',
min_price_value NUMERIC(12,2),
min_margin_pct NUMERIC(5,2) DEFAULT 10,
max_price NUMERIC(12,2),
step_pct NUMERIC(5,2) DEFAULT 1.0,
step_abs NUMERIC(10,2),
cooldown_minutes INT DEFAULT 60,
is_active BOOLEAN DEFAULT TRUE
);
CREATE TABLE repricing_log (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT REFERENCES products(id),
marketplace VARCHAR(50),
old_price NUMERIC(12,2),
new_price NUMERIC(12,2),
reason TEXT,
rule_id BIGINT REFERENCES repricing_rules(id),
triggered_at TIMESTAMP DEFAULT NOW()
);
Така структура дозволяє гнучко налаштовувати стратегії: на рівні всього магазину (global), для категорії або для конкретного товару. У правилах обов'язкові обмежувачі — мінімальна ціна (fixed або % від собівартості), максимальна, крок зміни та кулдаун між публікаціями. Лог — не лише для налагодження, а й для аналітики.
Як отримувати ціни конкурентів: Ozon та Wildberries
Для Ozon використовуємо ендпоінт /v1/product/info/competitor-price згідно з Ozon API documentation. Він повертає ціни конкурентів у Buy Box разом із їхнім рейтингом. Приклад на PHP:
class OzonCompetitorPriceClient
{
public function getCompetitorPrices(string $offerId): array
{
$response = Http::withHeaders([
'Client-Id' => $this->clientId,
'Api-Key' => $this->apiKey,
])->post('https://api-seller.ozon.ru/v1/product/info/competitor-price', [
'offer_id' => $offerId,
]);
return $response->json('result', []);
}
}
Wildberries — картка товару та парсинг card.wb.ru (у деяких випадках API v2). Отримуємо масив sizes, де вказані ціни для різних розмірів, включаючи ціну конкурента.
Двигун репрайсингу: як приймається рішення
class RepricingEngine
{
public function calculateNewPrice(
Product $product,
string $marketplace,
RepricingRule $rule,
): ?PriceDecision {
$competitorData = $this->getCompetitorData($product, $marketplace);
$costPrice = $product->cost_price ?? 0;
$currentPrice = $this->getCurrentMarketplacePrice($product, $marketplace);
$decision = match ($rule->strategy) {
'min_price' => $this->strategyMinPrice($currentPrice, $competitorData, $rule, $costPrice),
'buy_box' => $this->strategyBuyBox($currentPrice, $competitorData, $rule, $costPrice),
'rule_based' => $this->strategyRuleBased($currentPrice, $competitorData, $rule, $costPrice),
default => null,
};
if (!$decision) return null;
// Перевірка cooldown
$lastChange = RepricingLog::where('product_id', $product->id)
->where('marketplace', $marketplace)
->where('triggered_at', '>=', now()->subMinutes($rule->cooldown_minutes))
->exists();
if ($lastChange) return null;
return $decision;
}
private function strategyMinPrice(
float $current, array $competitors, RepricingRule $rule, float $costPrice
): ?PriceDecision {
$competitorMin = collect($competitors)->min('price');
if (!$competitorMin) return null;
$floor = $this->calculateFloor($rule, $costPrice);
if ($competitorMin < $current) {
$newPrice = max($competitorMin, $floor);
if ($newPrice >= $current) return null;
return new PriceDecision(
newPrice: $newPrice,
reason: "Конкурент знизив ціну до {$competitorMin}",
);
}
if ($competitorMin > $current && $rule->max_price && $current < $rule->max_price) {
$newPrice = min($competitorMin - 1, $rule->max_price);
return new PriceDecision(
newPrice: $newPrice,
reason: "Конкурент підняв ціну до {$competitorMin}",
);
}
return null;
}
private function calculateFloor(RepricingRule $rule, float $costPrice): float
{
if ($rule->min_price_mode === 'fixed' && $rule->min_price_value) {
return $rule->min_price_value;
}
if ($rule->min_margin_pct && $costPrice > 0) {
return $costPrice * (1 + $rule->min_margin_pct / 100);
}
return 0;
}
}
Ключове — стратегія Min Price. Мінімальна ціна конкурента порівнюється з поточною: якщо конкурент дешевший — знижуємо до його ціни (але не нижче обчисленого floor); якщо конкурент дорожчий і є стеля — підвищуємо. Кулдаун запобігає частим стрибкам.
Як влаштований захист від цінових війн?
Цінова війна — коли два селери нескінченно знижують ціни один за одним. Наш детектор:
class PriceWarDetector
{
public function isWarring(int $productId, string $marketplace): bool
{
$changes = RepricingLog::where('product_id', $productId)
->where('marketplace', $marketplace)
->where('triggered_at', '>=', now()->subDay())
->count();
if ($changes >= 5) {
Cache::put("repricing.paused.{$productId}.{$marketplace}", true, now()->addHours(6));
Notification::send($this->admins, new PriceWarAlert($productId, $marketplace));
return true;
}
return false;
}
}
Поріг (5 змін за 24 години) налаштовується під категорію товару. Поки бот заморожений, можна вручну розібратися або підняти ціну. Цей механізм допомагає уникнути цінових війн у 90% випадків.
Моніторинг та звіти: SQL для аналітики
SELECT
p.name,
rl.marketplace,
COUNT(*) AS changes_count,
MIN(rl.new_price) AS min_price_today,
MAX(rl.new_price) AS max_price_today,
ROUND(AVG(rl.new_price), 2) AS avg_price_today
FROM repricing_log rl
JOIN products p ON p.id = rl.product_id
WHERE rl.triggered_at >= NOW() - INTERVAL '24 hours'
GROUP BY p.name, rl.marketplace
ORDER BY changes_count DESC;
Такий запит видає картину за день: які товари змінювалися часто, в якому діапазоні цін. Можна додати сегментацію за стратегією або категорією.
Розклад завдань
$schedule->job(new RunRepricingJob)->everyThirtyMinutes();
$schedule->job(new ResetRepricingCountersJob)->dailyAt('03:00');
Кожні 30 хвилин — запуск репрайсингу для активних правил. Вночі — скидання лічильників. Час запуску можна змінювати під пікові навантаження конкретного маркетплейсу.
Порівняння стратегій репрайсингу
| Стратегія | Опис | Коли використовувати |
|---|---|---|
| Min Price | Утримувати ціну на рівні мінімальної серед конкурентів | Висока конкуренція, товари з низькою диференціацією |
| Buy Box | Орієнтуватися на виграш у кошику (ціна + рейтинг) | Товари, де Buy Box дає 80% продажів |
| Rule-Based | Гнучкі умови (наприклад, знижувати на 5% при зниженні конкурента) | Складні сценарії з сезонністю або акціями |
| Margin Floor | Захист мінімальної маржі (ціна не нижче собівартості + відсоток) | Товари з низькою маржею |
Що входить у розробку репрайсинг-бота
Ми реалізуємо проект під ключ за 6–8 робочих днів. В обсяг робіт входить:
- Проектування схеми даних та двигуна базових стратегій (2 дні)
- Інтеграція з Ozon API (ціни конкурентів + публікація) (1–2 дні)
- Інтеграція з Wildberries API (+1 день)
- Механізм PriceWarDetector з cooldown та алертами (1 день)
- Інтерфейс управління правилами та лог змін (1–2 дні)
- Повна документація по API (схеми запитів/відповідей), інструкція з налаштування правил, навчання вашої команди базовому адмініструванню, підтримка при запуску.
Ми гарантуємо коректну інтеграцію з офіційними API — наш досвід включає понад 50 проектів з автоматизації маркетплейсів. Вартість фіксується в договорі та не змінюється в процесі. Вартість розраховується індивідуально.
Чек-лист: типові помилки при впровадженні репрайсингу
- Немає кулдауну — ціни змінюються кожні 5 хвилин, Ozon блокує API.
- Не вказано min_price — йдете в мінус по маржі.
- Забули про сторнування — якщо ціна нижче собівартості, бот має відразу зупинити правку.
- Не моніторите помилки — silent fail при публікації ціни призводить до того, що товар стоїть за старою ціною.
Зв'яжіться з нами, щоб обговорити ваш сценарій — ми надішлемо приблизне ТЗ та відповімо на запитання протягом дня.







