💰 Логика работы с валютами и наценками

Назад к списку Редактировать
Информация
Slug:
currencies-management.html
Обновлено:
30.05.2026 15:03
Размер:
14.18 KB
Путь:
/resources/docs/developer/currencies-management.html
Ссылки
Публичная ссылка:
Ссылка в админке:
💰 Логика работы с валютами и наценками

ℹ️ Назначение: Этот документ объясняет бизнес-логику определения типов транзакций, расчета наценок и синхронизации списка валют с внешними источниками.

Типы валют

В системе существует три типа валют, определяемых полем type в таблице currencies:

Тип Описание Примеры Точность (DECIMAL)
crypto Криптовалюты BTC, ETH, SOL, XRP 20,8
fiat Фиатные (государственные) валюты RUB, USD, EUR 20,2
token Стейблкоины и токены USDT, USDC, DAI, BUSD 20,8

Типы транзакций (Operation Types)

Система автоматически определяет тип транзакции при расчете курса. Это ключ для выбора правильной наценки.

Тип транзакции Код Описание Логика применения наценки Пример
USDT → другая крипта usdt_to_other_crypto Обмен стейблкоина USDT на другую криптовалюту Используется наценка из поля usdt_to_other_crypto_margin. Клиент продает USDT, поэтому применяется скидка (отрицательная наценка) от цены стакана. USDT → BTC
USDT → RUB usdt_to_rub Продажа USDT за российские рубли Используется наценка из usdt_to_rub_margin. Клиент продает USDT. USDT → RUB
RUB → USDT rub_to_usdt Покупка USDT за рубли Используется наценка из rub_to_usdt_margin. Клиент покупает USDT, поэтому применяется наценка (положительная) к цене стакана. RUB → USDT
RUB → другая крипта rub_to_other_crypto Покупка любой криптовалюты (кроме USDT) за рубли Используется наценка из rub_to_other_crypto_margin. Клиент покупает крипту. RUB → BTC
Другая крипта → RUB other_crypto_to_rub Продажа любой криптовалюты (кроме USDT) за рубли Используется наценка из other_crypto_to_rub_margin. Клиент продает крипту. BTC → RUB
Другая крипта → другая крипта other_crypto_to_other_crypto Обмен между двумя криптовалютами (ни одна не USDT) Используется наценка из other_crypto_to_other_crypto_margin. Клиент продает первую валюту. ETH → BTC

Принцип выбора стакана (Order Book):

  • bid_price (зеленый стакан): Используется, когда клиент продает (USDT, BTC и т.д.). Это лучшая цена, по которой система готова купить.
  • ask_price (красный стакан): Используется, когда клиент покупает (USDT, BTC и т.д.). Это лучшая цена, по которой система готова продать.
  • Наценка/скидка применяется к выбранной цене стакана.

Синхронизация валют с API

Система может автоматически пополнять список валют, получая его из внешнего источника (по умолчанию - Rapira.net).

Команда синхронизации

# Запуск синхронизации
php artisan currencies:sync

Логика команды (SyncCurrenciesCommand)

  1. Получение списка: Запрос к методу ExternalRate::getAllCurrenciesFromAPI().
  2. Сравнение: Сравниваются коды валют из API с кодами в таблице currencies.
  3. Определение типа: Для каждой новой валюты тип определяется автоматически:
    • crypto: BTC, ETH, LTC, XRP, DOGE, ADA, DOT, SOL, BNB, TRX, ETC, OP, TON, NOT.
    • token: USDT, USDC, DAI, BUSD (стейблкоины).
    • fiat: Все остальные (RUB, USD, EUR и т.д.).
  4. Создание записи: Новая валюта добавляется с флагом is_active = true.

Ручное добавление валюты

Если валюта не появилась через синхронизацию или требуется особая настройка, её можно добавить вручную через админ-панель (Валюты → Создать), заполнив все поля для наценок.

⚠️ Важно: После добавления новой валюты необходимо вручную настроить для неё наценки в соответствующих полях таблицы currencies (например, other_crypto_to_rub_margin). Иначе при расчете курса будет использоваться значение по умолчанию (0%).

Расчет конечного курса: формула

Конечный курс, который видит клиент, рассчитывается по следующему принципу:

1. Определяется тип транзакции (например, `other_crypto_to_rub`).
2. Из таблицы `currencies` для валюты `from_currency` выбирается соответствующая наценка.
3. Из таблицы `external_rates` берется актуальная цена стакана (bid или ask).
4. К базовой цене применяется наценка.

Итоговая формула для клиента, который ПРОДАЕТ (используется bid):
Финальный курс = bid_price * (1 - (margin_percentage / 100))

Итоговая формула для клиента, который ПОКУПАЕТ (используется ask):
Финальный курс = ask_price * (1 + (margin_percentage / 100))

Пример (BTC → RUB):

  • Тип транзакции: other_crypto_to_rub
  • Наценка (other_crypto_to_rub_margin): -2% (клиенту дают скидку 2% от рынка)
  • Bid цена из стакана: 4,200,000 RUB за 1 BTC
  • Расчет: 4,200,000 * (1 - (2 / 100)) = 4,200,000 * 0.98 = 4,116,000 RUB

Методы модели Currency для разработчиков

Основная логика инкапсулирована в модели App\Models\Currency:

Метод Назначение Возвращаемое значение
getSettingsForPair($fromCurrency, $toCurrency) Определяет тип транзакции и возвращает соответствующие настройки (margin, min_amount) для пары валют. Массив с ключами: transaction_type, margin, min_amount.
getOperationType($fromCurrency, $toCurrency) (static) Определяет код типа операции на основе кодов валют. Строка с кодом типа транзакции (например, 'usdt_to_rub').

Обновление курсов

Для актуализации цен в стакане (external_rates) используется команда:

# Обновить все курсы
php artisan exchange-rates:update

# Обновить курсы из конкретного API
php artisan exchange-rates:update --api=1

Команда обращается к сервису ExternalRateService, который, в свою очередь, использует конфигурации из таблицы exchange_apis.

Поиск и устранение неисправностей

Проблема Возможная причина Решение
Для пары валют не находится курс 1. Валюты не синхронизированы с API.
2. Нет записи в external_rates для этой пары.
3. Не настроены наценки в таблице currencies.
1. Запустить currencies:sync.
2. Проверить наличие пары в external_rates после обновления курсов.
3. Проверить и заполнить поля наценок для валюты "отдаете".
Курс рассчитан неверно (например, 0 или 1) 1. Неверно определен тип транзакции.
2. В external_rates нулевые значения bid/ask.
1. Проверить логику в Currency::getOperationType().
2. Проверить данные в стакане, возможно проблема с API источником.
Минимальная сумма не работает Не заполнено поле min_amount для соответствующего типа транзакции в таблице currencies. Заполнить поле min_amount в админ-панели или напрямую в БД.
Последнее обновление: 30.05.2026 в 15:03
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>💰 Логика работы с валютами и наценками</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; padding: 20px; max-width: 1200px; margin: 0 auto; }
        h1 { color: #2c3e50; border-bottom: 2px solid #eee; padding-bottom: 10px; margin-bottom: 20px; }
        h2 { color: #34495e; border-bottom: 1px solid #eee; padding-bottom: 5px; margin-top: 30px; }
        h3 { color: #7f8c8d; margin-top: 25px; }
        p { margin-bottom: 15px; }
        ul, ol { margin-bottom: 15px; padding-left: 20px; }
        li { margin-bottom: 5px; }
        code { background-color: #f8f9fa; padding: 2px 6px; border-radius: 4px; font-family: monospace; }
        pre { background-color: #f8f9fa; padding: 15px; border-radius: 8px; overflow-x: auto; border: 1px solid #dee2e6; }
        .warning { background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
        .info { background-color: #d1ecf1; border: 1px solid #bee5eb; padding: 15px; border-radius: 5px; margin: 15px 0; }
        table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
        th, td { padding: 12px; border: 1px solid #dee2e6; }
        th { background-color: #f8f9fa; }
        .transaction-type { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; background-color: #e9ecef; }
    </style>
</head>
<body>
<h1>💰 Логика работы с валютами и наценками</h1>

<div class="info">
    <p><strong>ℹ️ Назначение:</strong> Этот документ объясняет бизнес-логику определения типов транзакций, расчета наценок и синхронизации списка валют с внешними источниками.</p>
</div>

<h2>Типы валют</h2>
<p>В системе существует три типа валют, определяемых полем <code>type</code> в таблице <code>currencies</code>:</p>
<table>
    <thead>
    <tr>
        <th>Тип</th>
        <th>Описание</th>
        <th>Примеры</th>
        <th>Точность (DECIMAL)</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td><strong>crypto</strong></td>
        <td>Криптовалюты</td>
        <td>BTC, ETH, SOL, XRP</td>
        <td>20,8</td>
    </tr>
    <tr>
        <td><strong>fiat</strong></td>
        <td>Фиатные (государственные) валюты</td>
        <td>RUB, USD, EUR</td>
        <td>20,2</td>
    </tr>
    <tr>
        <td><strong>token</strong></td>
        <td>Стейблкоины и токены</td>
        <td>USDT, USDC, DAI, BUSD</td>
        <td>20,8</td>
    </tr>
    </tbody>
</table>

<h2>Типы транзакций (Operation Types)</h2>
<p>Система автоматически определяет тип транзакции при расчете курса. Это ключ для выбора правильной наценки.</p>

<table>
    <thead>
    <tr>
        <th>Тип транзакции</th>
        <th>Код</th>
        <th>Описание</th>
        <th>Логика применения наценки</th>
        <th>Пример</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td><span class="transaction-type">USDT → другая крипта</span></td>
        <td><code>usdt_to_other_crypto</code></td>
        <td>Обмен стейблкоина USDT на другую криптовалюту</td>
        <td>Используется наценка из поля <code>usdt_to_other_crypto_margin</code>. Клиент продает USDT, поэтому применяется <strong>скидка</strong> (отрицательная наценка) от цены стакана.</td>
        <td>USDT → BTC</td>
    </tr>
    <tr>
        <td><span class="transaction-type">USDT → RUB</span></td>
        <td><code>usdt_to_rub</code></td>
        <td>Продажа USDT за российские рубли</td>
        <td>Используется наценка из <code>usdt_to_rub_margin</code>. Клиент продает USDT.</td>
        <td>USDT → RUB</td>
    </tr>
    <tr>
        <td><span class="transaction-type">RUB → USDT</span></td>
        <td><code>rub_to_usdt</code></td>
        <td>Покупка USDT за рубли</td>
        <td>Используется наценка из <code>rub_to_usdt_margin</code>. Клиент покупает USDT, поэтому применяется <strong>наценка</strong> (положительная) к цене стакана.</td>
        <td>RUB → USDT</td>
    </tr>
    <tr>
        <td><span class="transaction-type">RUB → другая крипта</span></td>
        <td><code>rub_to_other_crypto</code></td>
        <td>Покупка любой криптовалюты (кроме USDT) за рубли</td>
        <td>Используется наценка из <code>rub_to_other_crypto_margin</code>. Клиент покупает крипту.</td>
        <td>RUB → BTC</td>
    </tr>
    <tr>
        <td><span class="transaction-type">Другая крипта → RUB</span></td>
        <td><code>other_crypto_to_rub</code></td>
        <td>Продажа любой криптовалюты (кроме USDT) за рубли</td>
        <td>Используется наценка из <code>other_crypto_to_rub_margin</code>. Клиент продает крипту.</td>
        <td>BTC → RUB</td>
    </tr>
    <tr>
        <td><span class="transaction-type">Другая крипта → другая крипта</span></td>
        <td><code>other_crypto_to_other_crypto</code></td>
        <td>Обмен между двумя криптовалютами (ни одна не USDT)</td>
        <td>Используется наценка из <code>other_crypto_to_other_crypto_margin</code>. Клиент продает первую валюту.</td>
        <td>ETH → BTC</td>
    </tr>
    </tbody>
</table>

<div class="info">
    <p><strong>Принцип выбора стакана (Order Book):</strong></p>
    <ul>
        <li><strong>bid_price (зеленый стакан):</strong> Используется, когда клиент <strong>продает</strong> (USDT, BTC и т.д.). Это лучшая цена, по которой система готова купить.</li>
        <li><strong>ask_price (красный стакан):</strong> Используется, когда клиент <strong>покупает</strong> (USDT, BTC и т.д.). Это лучшая цена, по которой система готова продать.</li>
        <li>Наценка/скидка применяется к выбранной цене стакана.</li>
    </ul>
</div>

<h2>Синхронизация валют с API</h2>
<p>Система может автоматически пополнять список валют, получая его из внешнего источника (по умолчанию - Rapira.net).</p>

<h3>Команда синхронизации</h3>
<pre><code># Запуск синхронизации
php artisan currencies:sync</code></pre>

<h3>Логика команды (SyncCurrenciesCommand)</h3>
<ol>
    <li><strong>Получение списка:</strong> Запрос к методу <code>ExternalRate::getAllCurrenciesFromAPI()</code>.</li>
    <li><strong>Сравнение:</strong> Сравниваются коды валют из API с кодами в таблице <code>currencies</code>.</li>
    <li><strong>Определение типа:</strong> Для каждой новой валюты тип определяется автоматически:
        <ul>
            <li><code>crypto</code>: BTC, ETH, LTC, XRP, DOGE, ADA, DOT, SOL, BNB, TRX, ETC, OP, TON, NOT.</li>
            <li><code>token</code>: USDT, USDC, DAI, BUSD (стейблкоины).</li>
            <li><code>fiat</code>: Все остальные (RUB, USD, EUR и т.д.).</li>
        </ul>
    </li>
    <li><strong>Создание записи:</strong> Новая валюта добавляется с флагом <code>is_active = true</code>.</li>
</ol>

<h3>Ручное добавление валюты</h3>
<p>Если валюта не появилась через синхронизацию или требуется особая настройка, её можно добавить вручную через админ-панель (<strong>Валюты → Создать</strong>), заполнив все поля для наценок.</p>

<div class="warning">
    <p><strong>⚠️ Важно:</strong> После добавления новой валюты необходимо вручную настроить для неё наценки в соответствующих полях таблицы <code>currencies</code> (например, <code>other_crypto_to_rub_margin</code>). Иначе при расчете курса будет использоваться значение по умолчанию (0%).</p>
</div>

<h2>Расчет конечного курса: формула</h2>
<p>Конечный курс, который видит клиент, рассчитывается по следующему принципу:</p>
<pre><code>1. Определяется тип транзакции (например, `other_crypto_to_rub`).
2. Из таблицы `currencies` для валюты `from_currency` выбирается соответствующая наценка.
3. Из таблицы `external_rates` берется актуальная цена стакана (bid или ask).
4. К базовой цене применяется наценка.

Итоговая формула для клиента, который ПРОДАЕТ (используется bid):
Финальный курс = bid_price * (1 - (margin_percentage / 100))

Итоговая формула для клиента, который ПОКУПАЕТ (используется ask):
Финальный курс = ask_price * (1 + (margin_percentage / 100))</code></pre>

<p><strong>Пример (BTC → RUB):</strong></p>
<ul>
    <li>Тип транзакции: <code>other_crypto_to_rub</code></li>
    <li>Наценка (<code>other_crypto_to_rub_margin</code>): -2% (клиенту дают скидку 2% от рынка)</li>
    <li>Bid цена из стакана: 4,200,000 RUB за 1 BTC</li>
    <li>Расчет: 4,200,000 * (1 - (2 / 100)) = 4,200,000 * 0.98 = <strong>4,116,000 RUB</strong></li>
</ul>

<h2>Методы модели Currency для разработчиков</h2>
<p>Основная логика инкапсулирована в модели <code>App\Models\Currency</code>:</p>

<table>
    <thead>
    <tr>
        <th>Метод</th>
        <th>Назначение</th>
        <th>Возвращаемое значение</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td><code>getSettingsForPair($fromCurrency, $toCurrency)</code></td>
        <td>Определяет тип транзакции и возвращает соответствующие настройки (margin, min_amount) для пары валют.</td>
        <td>Массив с ключами: <code>transaction_type</code>, <code>margin</code>, <code>min_amount</code>.</td>
    </tr>
    <tr>
        <td><code>getOperationType($fromCurrency, $toCurrency)</code> <em>(static)</em></td>
        <td>Определяет код типа операции на основе кодов валют.</td>
        <td>Строка с кодом типа транзакции (например, <code>'usdt_to_rub'</code>).</td>
    </tr>
    </tbody>
</table>

<h2>Обновление курсов</h2>
<p>Для актуализации цен в стакане (<code>external_rates</code>) используется команда:</p>
<pre><code># Обновить все курсы
php artisan exchange-rates:update

# Обновить курсы из конкретного API
php artisan exchange-rates:update --api=1</code></pre>
<p>Команда обращается к сервису <code>ExternalRateService</code>, который, в свою очередь, использует конфигурации из таблицы <code>exchange_apis</code>.</p>

<h2>Поиск и устранение неисправностей</h2>
<table>
    <thead>
    <tr>
        <th>Проблема</th>
        <th>Возможная причина</th>
        <th>Решение</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>Для пары валют не находится курс</td>
        <td>1. Валюты не синхронизированы с API.<br>2. Нет записи в <code>external_rates</code> для этой пары.<br>3. Не настроены наценки в таблице <code>currencies</code>.</td>
        <td>1. Запустить <code>currencies:sync</code>.<br>2. Проверить наличие пары в <code>external_rates</code> после обновления курсов.<br>3. Проверить и заполнить поля наценок для валюты "отдаете".</td>
    </tr>
    <tr>
        <td>Курс рассчитан неверно (например, 0 или 1)</td>
        <td>1. Неверно определен тип транзакции.<br>2. В <code>external_rates</code> нулевые значения bid/ask.</td>
        <td>1. Проверить логику в <code>Currency::getOperationType()</code>.<br>2. Проверить данные в стакане, возможно проблема с API источником.</td>
    </tr>
    <tr>
        <td>Минимальная сумма не работает</td>
        <td>Не заполнено поле <code>min_amount</code> для соответствующего типа транзакции в таблице <code>currencies</code>.</td>
        <td>Заполнить поле <code>min_amount</code> в админ-панели или напрямую в БД.</td>
    </tr>
    </tbody>
</table>
</body>
</html>