Создание собственного коннектора
Пошаговое руководство по созданию коннектора к бирже для Barfinex — от создания проекта до регистрации и развёртывания.
Обзор
Barfinex использует плагинную архитектуру для подключения к биржам. Каждая биржа, брокер или источник данных — это отдельный npm-пакет (libs/exchange-*), реализующий стандартный набор интерфейсов. Сервис Provider загружает коннекторы динамически во время выполнения через NestJS LazyModuleLoader — изменения в основном коде не требуются.
Это руководство проведёт вас через создание коннектора с нуля, публикацию как npm-пакета и регистрацию в вашем экземпляре Barfinex.
Что вы получите
Собственный коннектор даёт вашему экземпляру Barfinex:
- Рыночные данные в реальном времени — сделки, снимки стакана, свечи через WebSocket
- Управление аккаунтом — балансы, позиции, снимки портфеля
- Исполнение ордеров — открытие, закрытие, модификация через единый API
- Обнаружение инструментов — автоматический список торговых пар/символов
- Полная интеграция — ваш коннектор работает с Detector-стратегиями, LLM-решениями Advisor, риск-менеджментом Inspector и дашбордом Studio из коробки
Архитектура
┌─────────────────────────────────────────────────┐ │ Provider (порт 8081) │ │ │ │ ExchangeConnectorFactory │ │ └── LazyModuleLoader │ │ ├── @barfinex/exchange-binance │ │ ├── @barfinex/exchange-kraken │ │ ├── @barfinex/exchange-alpaca │ │ └── @your-org/exchange-myexchange ◄── │ Ваш коннектор │ │ │ ExchangeConnectorRegistry │ │ └── Map<"type:market", ExchangeConnectorBundle>│ │ │ │ ExchangeManagerService │ │ └── activate / deactivate / status / catalog │ └─────────────────────────────────────────────────┘
Каждый коннектор предоставляет ExchangeConnectorBundle, состоящий из:
| Компонент | Интерфейс | Ответственность |
|---|---|---|
| Client Service | IExchangeClientService | Жизненный цикл соединения, проверка готовности |
| WebSocket Manager | IExchangeWsManager | Все потоковые подписки |
| Account API | IExchangeAccountApi | Балансы, позиции, информация об аккаунте |
| Market API | IExchangeMarketApi | Инструменты, цены |
| Order API | IExchangeOrderApi | Исполнение ордеров |
| Adapters | ExchangeAdapterSet | Трансформация сырых данных в нормализованный формат |
Требования
- Node.js 18+
- TypeScript 5+
- Базовые знания NestJS-модулей
- Доступ к API-документации биржи
- Работающий экземпляр Barfinex (для тестирования)
Шаг 1. Создание проекта
Создайте новую библиотеку в монорепо или как отдельный npm-пакет:
# Вариант A: Внутри монорепо mkdir libs/exchange-myexchange cd libs/exchange-myexchange # Вариант B: Отдельный npm-пакет mkdir barfinex-exchange-myexchange cd barfinex-exchange-myexchange npm init -y
Рекомендуемая структура каталогов:
exchange-myexchange/ ├── src/ │ ├── index.ts # Публичные экспорты │ ├── exchange-myexchange.module.ts # NestJS-модуль │ ├── client/ │ │ └── myexchange-client.service.ts # Управление соединением │ ├── ws/ │ │ └── myexchange-ws.manager.ts # WebSocket-подписки │ ├── api/ │ │ ├── myexchange-account.api.ts # REST-эндпоинты аккаунта │ │ ├── myexchange-market.api.ts # REST-эндпоинты рынка │ │ └── myexchange-order.api.ts # Исполнение ордеров │ ├── adapters/ │ │ ├── trade.adapter.ts # Сырые данные → Trade │ │ ├── orderbook.adapter.ts # Сырые данные → OrderBook │ │ ├── candle.adapter.ts # Сырые данные → Candle │ │ ├── account.adapter.ts # Сырые данные → Account/Balance │ │ ├── instruments.adapter.ts # Сырые данные → Instrument │ │ └── instrument-prices.adapter.ts # Сырые данные → Price │ └── utils/ │ └── timeframe.converter.ts # TimeFrame → интервал биржи ├── package.json └── tsconfig.json
Установка зависимостей
npm install @barfinex/types @nestjs/common rxjs npm install -D typescript @types/node
Шаг 2. Реализация интерфейсов
2.1 Client Service
Клиентский сервис управляет жизненным циклом соединения:
// src/client/myexchange-client.service.ts
import { Injectable, Logger } from '@nestjs/common';
import type { IExchangeClientService } from '@barfinex/types';
import type { MarketType } from '@barfinex/types';
@Injectable()
export class MyExchangeClientService implements IExchangeClientService {
private readonly logger = new Logger(MyExchangeClientService.name);
private ready = false;
private credentials: Record<string, string> = {};
setCredentials(creds: Record<string, string>) {
this.credentials = creds;
}
async ensureReady(timeoutMs = 10000): Promise<void> {
if (this.ready) return;
// Инициализация соединения с биржей
// Например: аутентификация, загрузка метаданных инструментов
this.logger.log('Подключение к MyExchange...');
// Ваша логика инициализации:
// await this.authenticate(this.credentials);
// await this.loadInstruments();
this.ready = true;
this.logger.log('Соединение с MyExchange установлено');
}
validateExchangeInstruments(
marketType: MarketType,
symbols: string[],
): { validInstruments: string[]; removedInstruments: string[] } {
// Проверка, что запрашиваемые символы существуют на бирже
const valid = symbols.filter((s) => this.isValidSymbol(s, marketType));
const removed = symbols.filter((s) => !this.isValidSymbol(s, marketType));
return { validInstruments: valid, removedInstruments: removed };
}
private isValidSymbol(symbol: string, marketType: MarketType): boolean {
// Ваша логика валидации
return true;
}
}
2.2 WebSocket Manager
WebSocket-менеджер обрабатывает все потоковые подписки:
// src/ws/myexchange-ws.manager.ts
import { Injectable, Logger } from '@nestjs/common';
import type {
IExchangeWsManager,
MarketType,
Instrument,
TimeFrame,
} from '@barfinex/types';
@Injectable()
export class MyExchangeWsManager implements IExchangeWsManager {
private readonly logger = new Logger(MyExchangeWsManager.name);
async subscribeToTrade(
options: { marketType: MarketType; instruments: Instrument[] },
handler: (trade: any) => void,
): Promise<() => void> {
// Подключение к WebSocket-потоку сделок биржи
// Парсинг сообщений и вызов handler(normalizedTrade)
const symbols = options.instruments.map((i) => i.symbol);
this.logger.log(`Подписка на сделки: ${symbols.join(', ')}`);
// Ваша логика WebSocket-подписки:
// const ws = new WebSocket(TRADE_STREAM_URL);
// ws.on('message', (msg) => handler(parseTradeMessage(msg)));
// Возврат функции отписки
return () => {
this.logger.log(`Отписка от сделок: ${symbols.join(', ')}`);
// ws.close();
};
}
async subscribeToOrderBook(
options: { marketType: MarketType; instruments: Instrument[] },
handler: any,
): Promise<() => void> {
// Аналогично subscribeToTrade, но для стакана заявок
return () => {};
}
async subscribeToCandles(
options: { marketType: MarketType; instruments: Instrument[]; interval: TimeFrame },
handler: any,
): Promise<() => void> {
// Подписка на поток свечей
return () => {};
}
async subscribeToAccount(
options: { marketType: MarketType },
handler: any,
): Promise<() => void> {
// Подписка на обновления аккаунта (изменения баланса, обновления позиций)
return () => {};
}
async subscribeToInstruments(
options: { marketType: MarketType },
handler: any,
): Promise<() => void> {
// Подписка на изменения списка инструментов
return () => {};
}
async subscribeToInstrumentPrices(
options: { marketType: MarketType; symbols?: string[] },
handler: any,
): Promise<() => void> {
// Подписка на обновления тикеров/цен
return () => {};
}
async destroy(): Promise<void> {
// Закрытие всех WebSocket-соединений
this.logger.log('Закрытие всех WebSocket-соединений');
}
}
2.3 Account API
// src/api/myexchange-account.api.ts
import { Injectable } from '@nestjs/common';
import type { IExchangeAccountApi, MarketType } from '@barfinex/types';
@Injectable()
export class MyExchangeAccountApi implements IExchangeAccountApi {
async getAssetsInfo(connectorType: string, marketType: MarketType) {
// Получение балансов и позиций через REST API биржи
return {
assets: [
// { currency: 'USDT', free: 1000, locked: 0, total: 1000 }
],
positions: [],
};
}
async getAccountInfo(connectorType: string, marketType: MarketType) {
// Получение метаданных аккаунта (разрешения, уровень комиссий и т.д.)
return {
id: 'my-account-id',
canTrade: true,
canWithdraw: true,
};
}
}
2.4 Market API
// src/api/myexchange-market.api.ts
import { Injectable } from '@nestjs/common';
import type { IExchangeMarketApi, MarketType } from '@barfinex/types';
@Injectable()
export class MyExchangeMarketApi implements IExchangeMarketApi {
async getInstrumentsInfo(connectorType: string, marketType: MarketType) {
// Возврат всех торгуемых инструментов для данного типа рынка
return [
// { symbol: 'BTCUSDT', baseAsset: 'BTC', quoteAsset: 'USDT', ... }
];
}
async getPrices(connectorType: string, marketType: MarketType) {
// Возврат текущих цен для всех инструментов
return {
// BTCUSDT: { value: 65000, moment: Date.now() }
};
}
}
2.5 Order API
// src/api/myexchange-order.api.ts
import { Injectable } from '@nestjs/common';
import type { IExchangeOrderApi } from '@barfinex/types';
@Injectable()
export class MyExchangeOrderApi implements IExchangeOrderApi {
async openOrder(params: any) {
// Отправка ордера на биржу
// Возврат нормализованного объекта Order
}
async closeOrder(params: any) {
// Закрытие конкретного ордера
}
async closeAllOrders(params: any) {
// Закрытие всех открытых ордеров
}
async getOpenOrders(params: any) {
// Возврат списка открытых ордеров
return [];
}
}
2.6 Адаптеры данных
Адаптеры трансформируют сырые данные биржи в нормализованный формат Barfinex:
// src/adapters/trade.adapter.ts
export function createTradeAdapter(handler: any) {
return (marketType: string) => (rawMessage: any) => {
// Трансформация сообщения о сделке в стандартный формат:
handler({
symbol: rawMessage.s, // например 'BTCUSDT'
price: parseFloat(rawMessage.p),
quantity: parseFloat(rawMessage.q),
timestamp: rawMessage.T,
side: rawMessage.m ? 'sell' : 'buy',
tradeId: rawMessage.t?.toString(),
});
};
}
// src/adapters/orderbook.adapter.ts
export function createOrderBookAdapter(handler: any) {
return (marketType: string) => (rawMessage: any) => {
handler({
symbol: rawMessage.s,
bids: rawMessage.bids?.map(([p, q]: string[]) => ({
price: parseFloat(p),
quantity: parseFloat(q),
})),
asks: rawMessage.asks?.map(([p, q]: string[]) => ({
price: parseFloat(p),
quantity: parseFloat(q),
})),
timestamp: rawMessage.T || Date.now(),
});
};
}
// src/adapters/candle.adapter.ts
export function createCandleAdapter(options: any) {
return (rawMessage: any) => {
options.handler({
symbol: rawMessage.s,
open: parseFloat(rawMessage.o),
high: parseFloat(rawMessage.h),
low: parseFloat(rawMessage.l),
close: parseFloat(rawMessage.c),
volume: parseFloat(rawMessage.v),
timestamp: rawMessage.t,
interval: options.interval,
});
};
}
2.7 Конвертер таймфреймов
Маппинг стандартного TimeFrame Barfinex на специфичные для биржи строки интервалов:
// src/utils/timeframe.converter.ts
import { TimeFrame } from '@barfinex/types';
const TIMEFRAME_MAP: Record<string, string> = {
[TimeFrame['1m']]: '1m',
[TimeFrame['5m']]: '5m',
[TimeFrame['15m']]: '15m',
[TimeFrame['1h']]: '1h',
[TimeFrame['4h']]: '4h',
[TimeFrame['1d']]: '1d',
[TimeFrame['1w']]: '1w',
};
export function convertTimeFrame(tf: TimeFrame): string {
return TIMEFRAME_MAP[tf] ?? '1m';
}
Шаг 3. Создание NestJS-модуля
// src/exchange-myexchange.module.ts
import { Module } from '@nestjs/common';
import { MyExchangeClientService } from './client/myexchange-client.service';
import { MyExchangeWsManager } from './ws/myexchange-ws.manager';
import { MyExchangeAccountApi } from './api/myexchange-account.api';
import { MyExchangeMarketApi } from './api/myexchange-market.api';
import { MyExchangeOrderApi } from './api/myexchange-order.api';
@Module({
providers: [
MyExchangeClientService,
MyExchangeWsManager,
MyExchangeAccountApi,
MyExchangeMarketApi,
MyExchangeOrderApi,
],
exports: [
MyExchangeClientService,
MyExchangeWsManager,
MyExchangeAccountApi,
MyExchangeMarketApi,
MyExchangeOrderApi,
],
})
export class ExchangeMyExchangeModule {}
Шаг 4. Публичная точка входа
Экспортируйте всё, что нужно фабрике для разрешения вашего коннектора:
// src/index.ts
export { ExchangeMyExchangeModule } from './exchange-myexchange.module';
export { MyExchangeClientService } from './client/myexchange-client.service';
export { MyExchangeWsManager } from './ws/myexchange-ws.manager';
export { MyExchangeAccountApi } from './api/myexchange-account.api';
export { MyExchangeMarketApi } from './api/myexchange-market.api';
export { MyExchangeOrderApi } from './api/myexchange-order.api';
export { createTradeAdapter } from './adapters/trade.adapter';
export { createOrderBookAdapter } from './adapters/orderbook.adapter';
export { createCandleAdapter } from './adapters/candle.adapter';
export { createAccountAdapter } from './adapters/account.adapter';
export { createInstrumentsAdapter } from './adapters/instruments.adapter';
export { createInstrumentPricesAdapter } from './adapters/instrument-prices.adapter';
export { convertTimeFrame } from './utils/timeframe.converter';
Шаг 5. Добавьте ConnectorType
Добавьте вашу биржу в enum ConnectorType в libs/types/src/connector.interface.ts:
export enum ConnectorType {
// ...существующие типы...
myexchange = 'myexchange',
}
Шаг 6. Регистрация в фабрике
Добавьте запись загрузчика в apps/provider/src/connector/exchange-manager/exchange-connector.factory.ts:
[ConnectorType.myexchange]: async () => {
const mod = await import('@your-org/exchange-myexchange');
return {
module: mod.ExchangeMyExchangeModule,
tokens: {
client: mod.MyExchangeClientService,
ws: mod.MyExchangeWsManager,
accountApi: mod.MyExchangeAccountApi,
marketApi: mod.MyExchangeMarketApi,
orderApi: mod.MyExchangeOrderApi,
},
adapters: {
createTradeAdapter: mod.createTradeAdapter,
createOrderBookAdapter: mod.createOrderBookAdapter,
createCandleAdapter: mod.createCandleAdapter,
createAccountAdapter: mod.createAccountAdapter,
createInstrumentsAdapter: mod.createInstrumentsAdapter,
createInstrumentPricesAdapter: mod.createInstrumentPricesAdapter,
convertTimeFrame: mod.convertTimeFrame,
},
};
},
Шаг 7. Добавление в каталог бирж
Зарегистрируйте вашу биржу в libs/types/src/exchange-catalog.interface.ts:
{
connectorType: ConnectorType.myexchange,
displayName: 'My Exchange',
segment: ExchangeSegment.CRYPTO_MAJOR, // или подходящий сегмент
supportedMarkets: [MarketType.spot, MarketType.futures],
credentials: [
{ key: 'apiKey', label: 'API Key', type: 'text', required: true },
{ key: 'apiSecret', label: 'API Secret', type: 'password', required: true },
],
website: 'https://www.myexchange.com',
description: 'Описание вашей биржи',
maturity: ExchangeMaturity.BETA,
features: {
spotTrading: true,
futuresTrading: true,
marginTrading: false,
orderbookStreaming: true,
tradeStreaming: true,
candleStreaming: true,
historicalData: true,
orderExecution: true,
testnetAvailable: false,
},
},
Шаг 8. Публикация и установка
Как npm-пакет (рекомендуется для внешних контрибьюций):
# Сборка npm run build # Публикация npm publish --access public
Затем в вашем экземпляре Barfinex:
npm install @your-org/exchange-myexchange
Как библиотека монорепо:
Добавьте библиотеку в nest-cli.json:
{
"projects": {
"exchange-myexchange": {
"type": "library",
"root": "libs/exchange-myexchange",
"entryFile": "index",
"sourceRoot": "libs/exchange-myexchange/src"
}
}
}
Шаг 9. Тестирование коннектора
Юнит-тесты
Тестируйте каждый адаптер независимо:
import { createTradeAdapter } from '../src/adapters/trade.adapter';
describe('Trade Adapter', () => {
it('должен нормализовать сырое сообщение о сделке', () => {
const handler = jest.fn();
const adapter = createTradeAdapter(handler)('spot');
adapter({
s: 'BTCUSDT',
p: '65000.50',
q: '0.001',
T: 1711800000000,
m: false,
t: 12345,
});
expect(handler).toHaveBeenCalledWith({
symbol: 'BTCUSDT',
price: 65000.50,
quantity: 0.001,
timestamp: 1711800000000,
side: 'buy',
tradeId: '12345',
});
});
});
Интеграционный тест с Provider
# Запуск Provider
npm run start:dev -- provider
# Активация коннектора через API
curl -X POST http://localhost:8081/api/exchanges/activate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"connectorType": "myexchange",
"marketTypes": ["spot"],
"credentials": {
"apiKey": "YOUR_KEY",
"apiSecret": "YOUR_SECRET"
}
}'
# Проверка статуса
curl http://localhost:8081/api/exchanges/active \
-H "Authorization: Bearer YOUR_TOKEN"
Справочник: полные контракты интерфейсов
IExchangeClientService
| Метод | Возвращает | Описание |
|---|---|---|
ensureReady(timeoutMs?) | Promise<void> | Инициализация соединения, исключение при таймауте |
validateExchangeInstruments(marketType, symbols) | { validInstruments, removedInstruments } | Валидация списка символов |
IExchangeWsManager
| Метод | Возвращает | Описание |
|---|---|---|
subscribeToTrade(options, handler) | Promise<() => void> | Поток сделок, возврат функции отписки |
subscribeToOrderBook(options, handler) | Promise<() => void> | Поток обновлений стакана |
subscribeToCandles(options, handler) | Promise<() => void> | Поток свечей |
subscribeToAccount(options, handler) | Promise<() => void> | Поток обновлений баланса/позиций |
subscribeToInstruments(options, handler) | Promise<() => void> | Поток изменений инструментов |
subscribeToInstrumentPrices(options, handler) | Promise<() => void> | Поток тикеров |
destroy() | Promise<void> | Очистка всех соединений |
ExchangeAdapterSet
| Функция | Сигнатура |
|---|---|
createTradeAdapter | (handler) => (marketType) => (msg) => void |
createOrderBookAdapter | (handler) => (marketType) => (msg) => void |
createCandleAdapter | (options) => (msg) => void |
createAccountAdapter | (options, handler) => (marketType) => (msg) => void |
createInstrumentsAdapter | (options) => (msg) => void |
createInstrumentPricesAdapter | (logger, handler) => (marketType) => (msg) => void |
convertTimeFrame | (tf: TimeFrame) => string | number |
Доступные сегменты бирж
| Сегмент | Для чего |
|---|---|
CRYPTO_MAJOR | Крупнейшие криптобиржи (Binance, Kraken, Coinbase) |
CRYPTO_DERIVATIVES | Деривативные платформы (Deribit, Bitget) |
CRYPTO_OTHER | Небольшие криптобиржи |
US_BROKER | Американские брокеры (Alpaca, Interactive Brokers) |
EU_BROKER | Европейские брокеры (eToro, DEGIRO) |
APAC_BROKER | Азиатско-Тихоокеанские брокеры (Tiger Brokers, Futu) |
CIS_BROKER | Брокеры СНГ (Тинькофф) |
KZ_BROKER | Казахстанские брокеры (KASE, Freedom Finance) |
FOREX | Форекс-брокеры (OANDA, Pepperstone) |
FUTURES_DERIVATIVES | Традиционные фьючерсы (CME, ICE) |
OTC_INSTITUTIONAL | OTC / Институциональные дески (Wintermute, Cumberland) |
MARKET_DATA | Поставщики данных (Polygon.io, Tiingo) |
DEX | Децентрализованные биржи (Uniswap, SushiSwap) |
Советы и лучшие практики
- Начните с данных — реализуйте потоки сделок и стакана сначала, добавьте исполнение ордеров позже
- Используйте тестнет — если биржа предлагает тестовую/песочницу среду, используйте её при разработке
- Обработка переподключений — WebSocket-соединения будут разрываться; реализуйте автоматическое переподключение с экспоненциальной задержкой
- Нормализация символов — маппинг специфичных для биржи форматов символов (например,
BTC_USDT) в стандарт Barfinex (BTCUSDT) - Ограничение частоты запросов — соблюдайте лимиты API биржи; реализуйте очередь запросов при необходимости
- Маппинг ошибок — преобразуйте коды ошибок биржи в понятные сообщения
- Логирование — используйте NestJS
Loggerдля структурированного логирования, интегрируемого с конвейером логов Provider
Следующие шаги
- Справочник Provider API — полная документация REST API
- Обзор архитектуры — как сервисы взаимодействуют
- Движок правил Detector — создание стратегий, использующих данные вашего коннектора
- Возможности Studio — просмотр вашего коннектора в дашборде