Barfinex

Создание собственного коннектора

Пошаговое руководство по созданию коннектора к бирже для 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 ServiceIExchangeClientServiceЖизненный цикл соединения, проверка готовности
WebSocket ManagerIExchangeWsManagerВсе потоковые подписки
Account APIIExchangeAccountApiБалансы, позиции, информация об аккаунте
Market APIIExchangeMarketApiИнструменты, цены
Order APIIExchangeOrderApiИсполнение ордеров
AdaptersExchangeAdapterSetТрансформация сырых данных в нормализованный формат

Требования

  • 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_INSTITUTIONALOTC / Институциональные дески (Wintermute, Cumberland)
MARKET_DATAПоставщики данных (Polygon.io, Tiingo)
DEXДецентрализованные биржи (Uniswap, SushiSwap)

Советы и лучшие практики

  1. Начните с данных — реализуйте потоки сделок и стакана сначала, добавьте исполнение ордеров позже
  2. Используйте тестнет — если биржа предлагает тестовую/песочницу среду, используйте её при разработке
  3. Обработка переподключений — WebSocket-соединения будут разрываться; реализуйте автоматическое переподключение с экспоненциальной задержкой
  4. Нормализация символов — маппинг специфичных для биржи форматов символов (например, BTC_USDT) в стандарт Barfinex (BTCUSDT)
  5. Ограничение частоты запросов — соблюдайте лимиты API биржи; реализуйте очередь запросов при необходимости
  6. Маппинг ошибок — преобразуйте коды ошибок биржи в понятные сообщения
  7. Логирование — используйте NestJS Logger для структурированного логирования, интегрируемого с конвейером логов Provider

Следующие шаги

Давайте свяжемся

Есть вопросы или хотите узнать больше о Barfinex? Напишите нам.