💰 Логика работы с валютами и наценками
Информация
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)
- Получение списка: Запрос к методу
ExternalRate::getAllCurrenciesFromAPI(). - Сравнение: Сравниваются коды валют из API с кодами в таблице
currencies. - Определение типа: Для каждой новой валюты тип определяется автоматически:
crypto: BTC, ETH, LTC, XRP, DOGE, ADA, DOT, SOL, BNB, TRX, ETC, OP, TON, NOT.token: USDT, USDC, DAI, BUSD (стейблкоины).fiat: Все остальные (RUB, USD, EUR и т.д.).
- Создание записи: Новая валюта добавляется с флагом
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 в админ-панели или напрямую в БД. |
<!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>