tigusigalpa / bcs-trade-php
PHP/Laravel client for BCS Broker Trade API
v1.0.0
2026-02-27 05:24 UTC
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
- ramsey/uuid: ^4.7
- ratchet/pawl: ^0.4
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^7.0|^8.0|^9.0|^10.0
- pestphp/pest: ^2.0
README
PHP/Laravel клиент для BCS Trade API — торгового API брокера БКС. Работает по HTTP и WebSocket.
Возможности
- Все HTTP эндпоинты BCS Trade API
- Все WebSocket каналы (real-time данные)
- Автообновление OAuth 2.0 токенов
- Кастомные исключения для разных типов ошибок
- Повторные попытки при сетевых сбоях
- Обработка rate limiting (HTTP 429)
- Валидация запросов (лимит 1000 баров для свечей)
- Автопагинация при запросе больших объёмов исторических данных
- PHP 8.1+ (enums, readonly properties)
- Laravel 9, 10, 11, 12
- PSR-12
- Тесты на Pest
Требования
- PHP 8.1 или выше
- Laravel 9.x, 10.x, 11.x или 12.x
- Composer
Установка
Через Composer:
composer require tigusigalpa/bcs-trade-php
Публикация конфига:
php artisan vendor:publish --tag=bcs-trade-config
Конфигурация
Получение Refresh Token
- Войдите в БКС Мир Инвестиций
- Перейдите в раздел API
- Создайте новый токен и скопируйте
refresh_token - Токен действителен 90 дней
Переменные окружения
В .env:
BCS_TRADE_REFRESH_TOKEN=your_refresh_token_here BCS_TRADE_CLIENT_ID=trade-api-read # или trade-api-write для торговых операций BCS_TRADE_CACHE_STORE=file
Параметры конфигурации
config/bcs-trade.php:
| Параметр | Описание | По умолчанию |
|---|---|---|
refresh_token |
Refresh token из БКС Мир Инвестиций | - |
client_id |
Scope токена: trade-api-read или trade-api-write |
trade-api-read |
cache_store |
Хранилище кеша для токенов | file |
cache_prefix |
Префикс ключей кеша | bcs_trade_ |
timeout |
Таймаут HTTP запросов (секунды) | 30 |
connect_timeout |
Таймаут подключения (секунды) | 10 |
retry_attempts |
Количество повторных попыток | 3 |
retry_delay |
Задержка между попытками (мс) | 500 |
Использование
Базовая инициализация
use Tigusigalpa\BCSTrade\BCSTradeClient; // Через конструктор $client = new BCSTradeClient(config('bcs-trade')); // Через Laravel Container $client = app(BCSTradeClient::class); // Через Facade use Tigusigalpa\BCSTrade\Facades\BCSTrade; $limits = BCSTrade::limits()->get();
HTTP API
Аутентификация
use Tigusigalpa\BCSTrade\Http\Responses\TokenResponse; // Получить токен вручную $tokenResponse = $client->auth()->getToken( 'your_refresh_token', 'trade-api-read' ); echo $tokenResponse->accessToken; echo $tokenResponse->expiresIn; // Токены управляются автоматически, ручной вызов не требуется
Лимиты портфеля
$limits = $client->limits()->get(); // Структура ответа: // [ // 'depoLimit' => [...], // 'futureHolding' => [...], // 'moneyLimits' => [...], // 'futuresLimits' => [...] // ]
Портфель
$portfolio = $client->portfolio()->get();
Справочная информация
// Расписание торгов $schedule = $client->information()->getDailySchedule('TQBR', 'SBER'); // Инструменты по ISIN $instruments = $client->information()->getByIsins(['RU0009029540']); // Инструмент по тикеру $instrument = $client->information()->getByTicker('SBER', 'TQBR'); // Инструменты по типу $stocks = $client->information()->getByType('stock'); // Статусы торгов $statuses = $client->information()->getTradingStatuses( ['SBER', 'GAZP'], ['TQBR', 'TQBR'] );
Рыночные данные (свечи)
use Tigusigalpa\BCSTrade\Enums\TimeFrame; // Получить исторические свечи (до 1000 баров) $candles = $client->marketData()->getCandles( classCode: 'TQBR', ticker: 'SBER', startDate: '2025-01-01T07:00:00Z', endDate: '2025-01-31T20:00:00Z', timeFrame: TimeFrame::D ); // Или используйте строку $candles = $client->marketData()->getCandles( 'TQBR', 'SBER', '2025-01-01T07:00:00Z', '2025-01-31T20:00:00Z', 'H1' ); // Автоматическая пагинация для больших диапазонов $candles = $client->marketData()->getCandlesPaginated( 'TQBR', 'SBER', '2024-01-01T07:00:00Z', '2025-12-31T20:00:00Z', TimeFrame::D ); // Доступные таймфреймы TimeFrame::M1 // 1 минута TimeFrame::M5 // 5 минут TimeFrame::M15 // 15 минут TimeFrame::M30 // 30 минут TimeFrame::H1 // 1 час TimeFrame::H4 // 4 часа TimeFrame::D // День TimeFrame::W // Неделя TimeFrame::MN // Месяц
Торговые операции (Orders)
Важно: Для торговых операций требуется client_id = 'trade-api-write'
use Tigusigalpa\BCSTrade\Enums\OrderSide; use Tigusigalpa\BCSTrade\Enums\OrderType; // Создать рыночный ордер на покупку $order = $client->orders()->create([ 'side' => OrderSide::Buy, 'orderType' => OrderType::Market, 'orderQuantity' => 10, 'ticker' => 'SBER', 'classCode' => 'TQBR', ]); // Создать лимитный ордер на продажу $order = $client->orders()->create([ 'side' => OrderSide::Sell, 'orderType' => OrderType::Limit, 'orderQuantity' => 5, 'ticker' => 'GAZP', 'classCode' => 'TQBR', 'price' => 150.50, ]); // Создать ордер с собственным UUID $order = $client->orders()->create([ 'clientOrderId' => 'my-unique-uuid', 'side' => '1', // или OrderSide::Buy 'orderType' => '2', // или OrderType::Limit 'orderQuantity' => 10, 'ticker' => 'SBER', 'classCode' => 'TQBR', 'price' => 250.00, ]); // Получить статус ордера $status = $client->orders()->status($clientOrderId); // Получить все ордера $allOrders = $client->orders()->all(); // Изменить ордер $updated = $client->orders()->edit($clientOrderId, [ 'orderQuantity' => 15, 'price' => 251.00, ]); // Отменить ордер $cancelled = $client->orders()->cancel($clientOrderId);
Сделки
// Получить все сделки $deals = $client->deals()->all();
Скидки на инструменты
// Получить скидки для всех инструментов $discounts = $client->discounts()->get(); // Получить скидки для конкретных тикеров $discounts = $client->discounts()->get(['SBER', 'GAZP']);
WebSocket API
Все WebSocket каналы поддерживают автоматическое переподключение с экспоненциальной задержкой.
Лимиты (Real-time)
$client->websocket()->limits(function ($data) { echo "Обновление лимитов: " . json_encode($data) . PHP_EOL; });
Портфель (Real-time)
$client->websocket()->portfolio(function ($data) { echo "Обновление портфеля: " . json_encode($data) . PHP_EOL; });
Маржинальные показатели
$client->websocket()->marginalIndicators(function ($data) { echo "Маржинальные показатели: " . json_encode($data) . PHP_EOL; });
Последняя свеча
$client->websocket()->lastCandle('TQBR', 'SBER', function ($data) { echo "Новая свеча SBER: " . json_encode($data) . PHP_EOL; });
Стакан котировок (Order Book)
$client->websocket()->orderBook('TQBR', 'SBER', function ($data) { echo "Обновление стакана SBER: " . json_encode($data) . PHP_EOL; });
Котировки
$client->websocket()->quotes(['TQBR:SBER', 'TQBR:GAZP'], function ($data) { echo "Котировки: " . json_encode($data) . PHP_EOL; });
Обезличенные сделки (All Trades)
$client->websocket()->allTrades('TQBR', 'SBER', function ($data) { echo "Новая сделка SBER: " . json_encode($data) . PHP_EOL; });
Статус исполнения ордеров
$client->websocket()->orderExecutionStatus(function ($data) { echo "Статус ордера: " . json_encode($data) . PHP_EOL; });
Статус транзакций
$client->websocket()->transactionStatus(function ($data) { echo "Статус транзакции: " . json_encode($data) . PHP_EOL; });
Обработка ошибок
Для каждого типа ошибок — своё исключение:
use Tigusigalpa\BCSTrade\Exceptions\BCSAuthException; use Tigusigalpa\BCSTrade\Exceptions\BCSRateLimitException; use Tigusigalpa\BCSTrade\Exceptions\BCSForbiddenException; use Tigusigalpa\BCSTrade\Exceptions\BCSApiException; try { $limits = $client->limits()->get(); } catch (BCSAuthException $e) { // Ошибка аутентификации (401) // Возможно, истек refresh_token (90 дней) echo "Ошибка аутентификации: " . $e->getMessage(); } catch (BCSForbiddenException $e) { // Доступ запрещен (403) // Проверьте scope токена (trade-api-read vs trade-api-write) echo "Доступ запрещен: " . $e->getMessage(); } catch (BCSRateLimitException $e) { // Превышен лимит запросов (429) $retryAfter = $e->getRetryAfter(); // секунды до повторной попытки echo "Rate limit. Повторить через: " . $retryAfter . " сек"; } catch (BCSApiException $e) { // Общая ошибка API (4xx, 5xx) $statusCode = $e->getStatusCode(); $responseBody = $e->getResponseBody(); echo "API ошибка {$statusCode}: " . json_encode($responseBody); }
Примеры использования
Пример 1: Получение текущей цены акции
$candles = $client->marketData()->getCandles( 'TQBR', 'SBER', date('Y-m-d\TH:i:s\Z', strtotime('-1 day')), date('Y-m-d\TH:i:s\Z'), TimeFrame::M5 ); $lastCandle = end($candles['bars']); $currentPrice = $lastCandle['close']; echo "Текущая цена SBER: {$currentPrice} руб.";
Пример 2: Создание и отслеживание ордера
use Ramsey\Uuid\Uuid; $clientOrderId = Uuid::uuid4()->toString(); // Создаем лимитный ордер $order = $client->orders()->create([ 'clientOrderId' => $clientOrderId, 'side' => OrderSide::Buy, 'orderType' => OrderType::Limit, 'orderQuantity' => 10, 'ticker' => 'SBER', 'classCode' => 'TQBR', 'price' => 250.00, ]); echo "Ордер создан: {$clientOrderId}\n"; // Проверяем статус $status = $client->orders()->status($clientOrderId); echo "Статус: " . json_encode($status) . "\n"; // Если нужно изменить цену $updated = $client->orders()->edit($clientOrderId, [ 'price' => 249.50, ]); echo "Ордер обновлен\n";
Пример 3: Мониторинг портфеля в реальном времени
// Запускаем WebSocket для отслеживания изменений портфеля $client->websocket()->portfolio(function ($data) { $totalValue = 0; foreach ($data['positions'] ?? [] as $position) { $totalValue += $position['marketValue'] ?? 0; } echo date('H:i:s') . " - Общая стоимость портфеля: {$totalValue} руб.\n"; });
Пример 4: Получение исторических данных за год
// Автоматическая пагинация для больших периодов $candles = $client->marketData()->getCandlesPaginated( 'TQBR', 'SBER', '2024-01-01T07:00:00Z', '2024-12-31T20:00:00Z', TimeFrame::D ); echo "Получено {$candles['bars']} свечей\n"; // Расчет доходности $firstPrice = $candles['bars'][0]['open']; $lastPrice = end($candles['bars'])['close']; $return = (($lastPrice - $firstPrice) / $firstPrice) * 100; echo "Доходность за год: " . number_format($return, 2) . "%\n";
Тестирование
Тесты написаны на Pest.
# Установить зависимости composer install # Запустить тесты ./vendor/bin/pest # Запустить тесты с покрытием ./vendor/bin/pest --coverage
Документация API
Документация BCS Trade API: https://trade-api.bcs.ru
Разделы:
- Ограничения API
- HTTP: Авторизация
- HTTP: Лимиты
- HTTP: Портфель
- HTTP: Справочная информация
- HTTP: Рыночные данные
- HTTP: Торговые операции
- HTTP: Сделки
- WebSocket: Обзор
Важные ограничения
- Refresh Token: Действителен 90 дней. После истечения необходимо получить новый через веб-интерфейс БКС.
- Исторические свечи: Максимум 1000 баров за один запрос (с 02.03.2026). Используйте
getCandlesPaginated()для больших периодов. - Rate Limiting: API имеет ограничения на количество запросов. Обрабатывайте
BCSRateLimitException. - Торговые операции: Требуют
client_id = 'trade-api-write'.
Changelog
См. CHANGELOG.md.
Лицензия
MIT. См. LICENSE.
Автор
Igor Sazonov
- Email: sovletig@gmail.com
- GitHub: @tigusigalpa
Поддержка
Баги и предложения — в Issues, пулл-реквесты — сюда.
Дисклеймер: пакет не является официальным продуктом БКС. Используйте на свой риск.