Розробка бота для автоматичного управління цінами на маркетплейсах (Repricing)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка бота для автоматичного управління цінами на маркетплейсах (Repricing)
Складна
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розроблення бота для автоматичного управління цінами на маркетплейсах (Repricing)

Repricing — автоматичне зміна цін на маркетплейсах у відповідь на дії конкурентів або ринкову ситуацію. Мета: утримати позицію в топі пошуку та Buy Box без ручного моніторингу. Неправильно настроєний repricing — це ціна ва до нуля або продажи нижче собівартості. Правильно настроєний — стабільний ріст конверсії при збереженні маржинальності.

Моделі репрайсингу

Стратегія Опис Коли застосовувати
Min Price Тримати ціну на рівні найкращого конкурента Конкурентний ринок без унікальної пропозиції
Buy Box Оптимізувати для перемоги в Buy Box на Ozon/WB Мультиселлерні позиції
Margin Floor Не опускатися нижче заданої маржи Завжди, як обмежувач
Rule-Based Набір умов (якщо конкурент < нашої ціни — знизити на X%) Гнучкі сценарії
Demand-Based Піднести ціну при високому попиту / залишках Товари з непостійним попитом

Схема даних

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', -- 'fixed' | '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()
);

Отримання конкурентних цін

Ozon API — цени конкурентів в Buy Box:

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 — аналіз через картку товару:

class WildberriesPriceClient
{
    public function getSellerPrices(int $nmId): array
    {
        // WB API v2 для отримання прайслиста конкурентів на товар
        $response = Http::get("https://card.wb.ru/cards/detail", [
            'nm'    => $nmId,
            'spp'   => 27,
            'curr'  => 'rub',
        ]);

        $products = $response->json('data.products', []);
        $product  = collect($products)->firstWhere('id', $nmId);

        return $product ? $product['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);

        // Конкурент дешевше — знизити до його ціни (не нижче floor)
        if ($competitorMin < $current) {
            $newPrice = max($competitorMin, $floor);
            if ($newPrice >= $current) return null; // немає сенсу

            return new PriceDecision(
                newPrice: $newPrice,
                reason:   "Конкурент знизив ціну до {$competitorMin}",
            );
        }

        // Конкурент дороже — можна піднести (не вище max_price)
        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;
    }
}

Публікація ціни через API

class OzonPricePublisher
{
    public function setPrice(string $offerId, float $newPrice): bool
    {
        $response = Http::withHeaders([
            'Client-Id' => $this->clientId,
            'Api-Key'   => $this->apiKey,
        ])->post('https://api-seller.ozon.ru/v1/product/import/prices', [
            'prices' => [[
                'offer_id'       => $offerId,
                'price'          => (string) $newPrice,
                'old_price'      => '0',
                'premium_price'  => '0',
                'price_strategy_enabled' => false,
            ]],
        ]);

        return $response->successful()
            && collect($response->json('result', []))->first()['updated'] === true;
    }
}

Захист від ціневих воєн

Ціна вйна — ситуація, коли два конкуренти нескінченно знижують ціни один одному. Механізми захисту:

class PriceWarDetector
{
    public function isWarring(int $productId, string $marketplace): bool
    {
        // Якщо ціна змінювалася більше 5 разів за 24 години — ознака води
        $changes = RepricingLog::where('product_id', $productId)
            ->where('marketplace', $marketplace)
            ->where('triggered_at', '>=', now()->subDay())
            ->count();

        if ($changes >= 5) {
            // Зупинити repricing на 6 годин і сповістити
            Cache::put("repricing.paused.{$productId}.{$marketplace}", true, now()->addHours(6));
            Notification::send($this->admins, new PriceWarAlert($productId, $marketplace));
            return true;
        }

        return false;
    }
}

Моніторинг та звіти

-- Статистика змін цін за день
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;

Розписання

// Запуск репрайсингу кожні 30 хвилин
$schedule->job(new RunRepricingJob)->everyThirtyMinutes();

// Вночі — скидання лічильників і пересчёт стратегій
$schedule->job(new ResetRepricingCountersJob)->dailyAt('03:00');

Графік реалізації

  • Схема даних + движок базових стратегій: 2 дні
  • Integracia з Ozon API (ціни конкурентів + публікація): 1–2 дні
  • Wildberries API: +1 день
  • PriceWarDetector + cooldown + alert: 1 день
  • Інтерфейс управління правилами + лог змін: 1–2 дні

Разом: 6–8 робочих днів.