Golang-клиент для BCS Trade API — торгового API БКС Брокера.
Пакет позволяет подключаться к бирже через БКС из Go-приложений: получать котировки и свечи, торговать акциями и облигациями на Московской бирже, следить за портфелем и ордерами в реальном времени через WebSocket. Подходит для написания торговых ботов, аналитических сервисов и любых систем, которым нужен программный доступ к брокеру.
Реализованы все HTTP-эндпоинты и все WebSocket-каналы из документации БКС. Токен обновляется автоматически, клиент потокобезопасен, все методы принимают context.Context.
BCS Trade API — относительно новый REST + WebSocket интерфейс от БКС для алготрейдинга и автоматизации торговли на MOEX. Документация есть, но готовых клиентов на Go до сих пор не было. Этот пакет закрывает этот пробел: не нужно вручную разбираться с OAuth2-токенами, пагинацией свечей и переподключением WebSocket — всё это уже сделано.
Если вы пишете торгового бота на Go, строите дашборд с рыночными данными Московской биржи или просто хотите автоматизировать свой портфель у БКС — можно не писать обёртку с нуля, а взять готовую.
go get github.com/tigusigalpa/bcs-trade-goТребуется Go 1.21+.
Для работы нужен refresh token, который можно получить в личном кабинете БКС Мир Инвестиций в разделе настроек API.
Есть два скоупа:
trade-api-read(по умолчанию) — чтение данных: котировки, портфель, справочникиtrade-api-write— торговля: создание, редактирование и отмена ордеров
Самый простой способ — задать переменные окружения:
export BCS_TRADE_REFRESH_TOKEN="ваш-refresh-token"
export BCS_TRADE_CLIENT_ID="trade-api-read" # необязательно, это значение по умолчаниюpackage main
import (
"context"
"fmt"
"log"
"github.com/tigusigalpa/bcs-trade-go"
)
func main() {
ctx := context.Background()
client, err := bcstrade.NewFromEnv(ctx)
if err != nil {
log.Fatal(err)
}
portfolio, err := client.Portfolio.Get(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Стоимость портфеля: %.2f %s\n",
portfolio.Summary.TotalValue,
portfolio.Summary.Currency)
}Если нужна ручная настройка:
cfg := &bcstrade.Config{
RefreshToken: "ваш-refresh-token",
ClientID: bcstrade.ClientIDWrite,
Timeout: 30 * time.Second,
RetryAttempts: 3,
RetryDelay: 500 * time.Millisecond,
}
client, err := bcstrade.New(ctx, cfg)params := models.GetCandlesParams{
ClassCode: "TQBR",
Ticker: "SBER",
StartDate: time.Now().AddDate(0, 0, -7),
EndDate: time.Now(),
TimeFrame: models.TimeFrameH1,
}
candles, err := client.MarketData.GetCandles(ctx, params)
if err != nil {
log.Fatal(err)
}
for _, bar := range candles.Bars {
fmt.Printf("%s: O=%.2f H=%.2f L=%.2f C=%.2f V=%.0f\n",
bar.Time.Format("2006-01-02 15:04"),
bar.Open, bar.High, bar.Low, bar.Close, bar.Volume)
}API отдаёт максимум 1000 баров за запрос. Если нужно больше — используйте GetCandlesPaginated, он сам разобьёт запрос на части и склеит результат:
candles, err := client.MarketData.GetCandlesPaginated(ctx, params)instrument, err := client.Information.GetInstrumentByTicker(ctx, "TQBR", "SBER")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s (%s), лот: %d\n", instrument.Name, instrument.ISIN, instrument.LotSize)
schedule, err := client.Information.GetDailySchedule(ctx, "TQBR", "SBER")Для торговли нужен скоуп trade-api-write.
price := 250.50
order := &models.CreateOrderRequest{
Side: models.OrderSideBuy,
OrderType: models.OrderTypeLimit,
OrderQuantity: 10,
Ticker: "SBER",
ClassCode: "TQBR",
Price: &price,
// ClientOrderID сгенерируется сам, если не задан
}
resp, err := client.Orders.Create(ctx, order)Отмена и проверка статуса:
_, err := client.Orders.Cancel(ctx, "order-uuid")
status, err := client.Orders.Status(ctx, "order-uuid")
fmt.Printf("%s: исполнено %d/%d\n", status.Status, status.FilledQuantity, status.OrderQuantity)Все WebSocket-методы возвращают два канала — данные и ошибки. Подписка живёт пока жив контекст. При обрыве соединения клиент переподключается сам с экспоненциальной задержкой.
Котировки:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
quoteCh, errCh, err := client.WebSocket.Quotes(ctx, []string{"TQBR:SBER", "TQBR:GAZP"})
if err != nil {
log.Fatal(err)
}
for {
select {
case q := <-quoteCh:
fmt.Printf("%s: Bid=%.2f Ask=%.2f Last=%.2f\n", q.Ticker, q.Bid, q.Ask, q.Last)
case err := <-errCh:
log.Printf("ws error: %v", err)
case <-ctx.Done():
return
}
}Статус исполнения ордеров:
execCh, errCh, err := client.WebSocket.OrderExecutionStatus(ctx)
for {
select {
case e := <-execCh:
fmt.Printf("Ордер %s: %s, заполнено %d, средняя %.2f\n",
e.ClientOrderID, e.Status, e.FilledQty, e.AveragePrice)
case err := <-errCh:
log.Printf("ws error: %v", err)
case <-ctx.Done():
return
}
}Аналогично работают OrderBook, LastCandle, AllTrades, Portfolio, Limits, MarginalIndicators, TransactionStatus.
Все ошибки типизированы, их можно проверять через errors.As:
resp, err := client.Orders.Create(ctx, order)
if err != nil {
var authErr *bcstrade.AuthError
var forbiddenErr *bcstrade.ForbiddenError
var rateLimitErr *bcstrade.RateLimitError
var validationErr *bcstrade.ValidationError
switch {
case errors.As(err, &authErr):
// refresh token протух — нужно получить новый в ЛК
case errors.As(err, &forbiddenErr):
// не тот скоуп (нужен trade-api-write)
case errors.As(err, &rateLimitErr):
// слишком частые запросы, в RetryAfter — сколько ждать
case errors.As(err, &validationErr):
// ошибка валидации до отправки запроса (нет обязательного поля и т.п.)
}
}При получении HTTP 401 клиент сам пробует обновить токен и повторить запрос — если не помогло, вернёт AuthError.
HTTP-эндпоинты — всё, что есть в BCS Trade REST API:
- Аутентификация (OAuth2, автоматический refresh)
- Портфель и лимиты счёта
- Справочники: инструменты по ISIN, тикеру или типу, расписание торгов, торговые статусы
- Исторические свечи (OHLCV) с автоматической пагинацией при запросе больше 1000 баров
- Ордера: выставление, редактирование, отмена, получение статуса и списка
- Сделки и скидки на инструменты
WebSocket-каналы — подписки на данные в реальном времени:
- Котировки (bid/ask/last) и лента анонимных сделок
- Стакан заявок (Level 2 order book)
- Свечи в реальном времени (последняя свеча по инструменту)
- Портфель, лимиты и маржинальные показатели
- Статус исполнения ордеров и транзакций
Подробное описание каждого метода — в документации на pkg.go.dev.
Несколько вещей, которые стоит знать:
- OAuth2 под капотом. Вы отдаёте refresh token — дальше клиент сам получает access token и обновляет его за минуту до истечения. При 401 пробует обновить токен и повторить запрос.
- Пагинация свечей. BCS Trade API с марта 2026 отдаёт не больше 1000 баров за раз.
GetCandlesPaginatedсам оценивает нужное количество запросов, выполняет их и склеивает результат. - WebSocket с переподключением. При обрыве связи клиент переподключается с экспоненциальной задержкой (от 1 до 60 секунд), каждый раз обновляя токен. Отмена контекста корректно закрывает соединение.
- Потокобезопасность. Токен хранится под мьютексом, клиентом можно пользоваться из нескольких горутин одновременно.
- UUID для ордеров. Если не передать
ClientOrderIDпри создании ордера — он сгенерируется автоматически (UUID v4).
go test ./... # базовый прогон
go test -race ./... # с детектором гонок
go test -cover ./... # с покрытиемgolang.org/x/oauth2github.com/google/uuidgithub.com/gorilla/websocketgithub.com/stretchr/testify(только тесты)
MIT. Подробности в файле LICENSE.
Автор — Игорь Сазонов (sovletig@gmail.com).
Это неофициальная библиотека, автор не связан с БКС Брокером.
Если вам нужен доступ к другим брокерам с Московской биржи из Go:
- invest-api-go-sdk — официальный SDK Т-Инвестиций (бывший Тинькофф Инвестиции)
- go-quik — работа с QUIK через DDE/Trans2Quik
Если знаете другие Go-библиотеки для российских брокеров — присылайте PR, добавлю.
