Barfinex

Building Custom Connectors

Step-by-step guide to creating a custom exchange connector for Barfinex — from project scaffolding to registration and deployment.

Overview

Barfinex uses a plugin architecture for exchange connectivity. Every exchange, broker, or data source is a separate npm package (libs/exchange-*) that implements a standard set of interfaces. The Provider service loads connectors dynamically at runtime using NestJS LazyModuleLoader — no changes to core code required.

This guide walks you through creating a connector from scratch, publishing it as an npm package, and registering it in your Barfinex instance.

What You Get

A custom connector gives your Barfinex instance:

  • Real-time market data — trades, orderbook snapshots, candlesticks via WebSocket
  • Account management — balances, positions, portfolio snapshots
  • Order execution — open, close, modify orders through a unified API
  • Instrument discovery — automatic listing of tradable pairs/symbols
  • Full integration — your connector works with Detector strategies, Advisor LLM decisions, Inspector risk management, and Studio dashboard out of the box

Architecture

┌─────────────────────────────────────────────────┐
│                  Provider (port 8081)            │
│                                                  │
│   ExchangeConnectorFactory                       │
│     └── LazyModuleLoader                         │
│           ├── @barfinex/exchange-binance          │
│           ├── @barfinex/exchange-kraken           │
│           ├── @barfinex/exchange-alpaca           │
│           └── @your-org/exchange-myexchange  ◄── │  Your connector
│                                                  │
│   ExchangeConnectorRegistry                      │
│     └── Map<"type:market", ExchangeConnectorBundle>│
│                                                  │
│   ExchangeManagerService                         │
│     └── activate / deactivate / status / catalog │
└─────────────────────────────────────────────────┘

Each connector provides an ExchangeConnectorBundle consisting of:

ComponentInterfaceResponsibility
Client ServiceIExchangeClientServiceConnection lifecycle, readiness check
WebSocket ManagerIExchangeWsManagerAll streaming subscriptions
Account APIIExchangeAccountApiBalances, positions, account info
Market APIIExchangeMarketApiInstruments, prices
Order APIIExchangeOrderApiOrder execution
AdaptersExchangeAdapterSetRaw → normalized data transforms

Prerequisites

  • Node.js 18+
  • TypeScript 5+
  • Basic NestJS module knowledge
  • Access to the exchange's API documentation
  • Barfinex instance running (for testing)

Step 1. Scaffold the Project

Create a new library in the monorepo or as a standalone npm package:

# Option A: Inside the monorepo
mkdir libs/exchange-myexchange
cd libs/exchange-myexchange

# Option B: Standalone npm package
mkdir barfinex-exchange-myexchange
cd barfinex-exchange-myexchange
npm init -y

Recommended directory structure:

exchange-myexchange/
├── src/
│   ├── index.ts                         # Public exports
│   ├── exchange-myexchange.module.ts    # NestJS module
│   ├── client/
│   │   └── myexchange-client.service.ts # Connection management
│   ├── ws/
│   │   └── myexchange-ws.manager.ts     # WebSocket subscriptions
│   ├── api/
│   │   ├── myexchange-account.api.ts    # Account REST endpoints
│   │   ├── myexchange-market.api.ts     # Market REST endpoints
│   │   └── myexchange-order.api.ts      # Order execution
│   ├── adapters/
│   │   ├── trade.adapter.ts             # Raw → Trade transform
│   │   ├── orderbook.adapter.ts         # Raw → OrderBook transform
│   │   ├── candle.adapter.ts            # Raw → Candle transform
│   │   ├── account.adapter.ts           # Raw → Account/Balance transform
│   │   ├── instruments.adapter.ts       # Raw → Instrument transform
│   │   └── instrument-prices.adapter.ts # Raw → Price transform
│   └── utils/
│       └── timeframe.converter.ts       # TimeFrame → exchange interval
├── package.json
└── tsconfig.json

Install Dependencies

npm install @barfinex/types @nestjs/common rxjs
npm install -D typescript @types/node

Step 2. Implement the Interfaces

2.1 Client Service

The client service manages the connection lifecycle:

// 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;

    // Initialize connection to the exchange
    // e.g. authenticate, load instrument metadata
    this.logger.log('Connecting to MyExchange...');

    // Your initialization logic here:
    // await this.authenticate(this.credentials);
    // await this.loadInstruments();

    this.ready = true;
    this.logger.log('MyExchange connection ready');
  }

  validateExchangeInstruments(
    marketType: MarketType,
    symbols: string[],
  ): { validInstruments: string[]; removedInstruments: string[] } {
    // Validate that requested symbols exist on the exchange
    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 {
    // Your validation logic
    return true;
  }
}

2.2 WebSocket Manager

The WebSocket manager handles all streaming subscriptions:

// 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> {
    // Connect to exchange WebSocket trade stream
    // Parse messages and call handler(normalizedTrade)

    const symbols = options.instruments.map((i) => i.symbol);
    this.logger.log(`Subscribing to trades: ${symbols.join(', ')}`);

    // Your WebSocket subscription logic here
    // const ws = new WebSocket(TRADE_STREAM_URL);
    // ws.on('message', (msg) => handler(parseTradeMessage(msg)));

    // Return unsubscribe function
    return () => {
      this.logger.log(`Unsubscribing from trades: ${symbols.join(', ')}`);
      // ws.close();
    };
  }

  async subscribeToOrderBook(
    options: { marketType: MarketType; instruments: Instrument[] },
    handler: any,
  ): Promise<() => void> {
    // Similar to subscribeToTrade but for orderbook depth
    return () => {};
  }

  async subscribeToCandles(
    options: { marketType: MarketType; instruments: Instrument[]; interval: TimeFrame },
    handler: any,
  ): Promise<() => void> {
    // Subscribe to kline/candlestick stream
    return () => {};
  }

  async subscribeToAccount(
    options: { marketType: MarketType },
    handler: any,
  ): Promise<() => void> {
    // Subscribe to account updates (balance changes, position updates)
    return () => {};
  }

  async subscribeToInstruments(
    options: { marketType: MarketType },
    handler: any,
  ): Promise<() => void> {
    // Subscribe to instrument list changes (new pairs, delistings)
    return () => {};
  }

  async subscribeToInstrumentPrices(
    options: { marketType: MarketType; symbols?: string[] },
    handler: any,
  ): Promise<() => void> {
    // Subscribe to ticker/price updates
    return () => {};
  }

  async destroy(): Promise<void> {
    // Close all WebSocket connections
    this.logger.log('Destroying all WebSocket connections');
  }
}

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) {
    // Fetch balances and positions from exchange REST API
    return {
      assets: [
        // { currency: 'USDT', free: 1000, locked: 0, total: 1000 }
      ],
      positions: [],
    };
  }

  async getAccountInfo(connectorType: string, marketType: MarketType) {
    // Fetch account metadata (permissions, fee tier, etc.)
    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 all tradable instruments for this market type
    return [
      // { symbol: 'BTCUSDT', baseAsset: 'BTC', quoteAsset: 'USDT', ... }
    ];
  }

  async getPrices(connectorType: string, marketType: MarketType) {
    // Return current prices for all instruments
    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) {
    // Submit order to exchange
    // Return normalized Order object
  }

  async closeOrder(params: any) {
    // Close specific order
  }

  async closeAllOrders(params: any) {
    // Close all open orders
  }

  async getOpenOrders(params: any) {
    // Return list of open orders
    return [];
  }
}

2.6 Data Adapters

Adapters transform raw exchange data into Barfinex's normalized format:

// src/adapters/trade.adapter.ts
export function createTradeAdapter(handler: any) {
  return (marketType: string) => (rawMessage: any) => {
    // Transform exchange-specific trade message into standard format:
    handler({
      symbol: rawMessage.s,        // e.g. '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,
    });
  };
}

// src/adapters/account.adapter.ts
export function createAccountAdapter(options: any, handler: any) {
  return (marketType: string) => (rawMessage: any) => {
    handler({
      assets: rawMessage.balances?.map((b: any) => ({
        currency: b.asset,
        free: parseFloat(b.free),
        locked: parseFloat(b.locked),
        total: parseFloat(b.free) + parseFloat(b.locked),
      })),
    });
  };
}

// src/adapters/instruments.adapter.ts
export function createInstrumentsAdapter(options: any) {
  return (rawMessage: any) => {
    // Transform instrument list updates
  };
}

// src/adapters/instrument-prices.adapter.ts
export function createInstrumentPricesAdapter(logger: any, handler: any) {
  return (marketType: string) => (rawMessage: any) => {
    handler({
      symbol: rawMessage.s,
      price: parseFloat(rawMessage.c),
      timestamp: rawMessage.E,
    });
  };
}

2.7 Timeframe Converter

Maps Barfinex's standard TimeFrame enum to exchange-specific interval strings:

// 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';
}

Step 3. Create the NestJS Module

// 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 {}

Step 4. Create the Public Entry Point

Export everything the factory needs to resolve your connector:

// 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';

Step 5. Add ConnectorType

Add your exchange to the ConnectorType enum in libs/types/src/connector.interface.ts:

export enum ConnectorType {
  // ...existing types...
  myexchange = 'myexchange',
}

Step 6. Register in the Factory

Add a loader entry in 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,
    },
  };
},

Step 7. Add to the Exchange Catalog

Register your exchange in libs/types/src/exchange-catalog.interface.ts:

{
  connectorType: ConnectorType.myexchange,
  displayName: 'My Exchange',
  segment: ExchangeSegment.CRYPTO_MAJOR, // or appropriate segment
  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: 'Description of your exchange',
  maturity: ExchangeMaturity.BETA,
  features: {
    spotTrading: true,
    futuresTrading: true,
    marginTrading: false,
    orderbookStreaming: true,
    tradeStreaming: true,
    candleStreaming: true,
    historicalData: true,
    orderExecution: true,
    testnetAvailable: false,
  },
},

Step 8. Publish & Install

# Build
npm run build

# Publish
npm publish --access public

Then in your Barfinex instance:

npm install @your-org/exchange-myexchange

As a monorepo library:

Add the library to nest-cli.json:

{
  "projects": {
    "exchange-myexchange": {
      "type": "library",
      "root": "libs/exchange-myexchange",
      "entryFile": "index",
      "sourceRoot": "libs/exchange-myexchange/src"
    }
  }
}

Step 9. Test Your Connector

Unit tests

Test each adapter independently:

import { createTradeAdapter } from '../src/adapters/trade.adapter';

describe('Trade Adapter', () => {
  it('should normalize a raw trade message', () => {
    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',
    });
  });
});

Integration test with Provider

# Start Provider
npm run start:dev -- provider

# Activate your connector via 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"
    }
  }'

# Check status
curl http://localhost:8081/api/exchanges/active \
  -H "Authorization: Bearer YOUR_TOKEN"

Reference: Full Interface Contracts

IExchangeClientService

MethodReturnsDescription
ensureReady(timeoutMs?)Promise<void>Initialize connection, throw on timeout
validateExchangeInstruments(marketType, symbols){ validInstruments, removedInstruments }Validate symbol list

IExchangeWsManager

MethodReturnsDescription
subscribeToTrade(options, handler)Promise<() => void>Stream trades, return unsubscribe fn
subscribeToOrderBook(options, handler)Promise<() => void>Stream orderbook updates
subscribeToCandles(options, handler)Promise<() => void>Stream candlesticks
subscribeToAccount(options, handler)Promise<() => void>Stream balance/position updates
subscribeToInstruments(options, handler)Promise<() => void>Stream instrument changes
subscribeToInstrumentPrices(options, handler)Promise<() => void>Stream ticker prices
destroy()Promise<void>Clean up all connections

ExchangeAdapterSet

FunctionSignature
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

Available Exchange Segments

SegmentUse for
CRYPTO_MAJORTop-tier crypto exchanges (Binance, Kraken, Coinbase)
CRYPTO_DERIVATIVESCrypto derivatives platforms (Deribit, Bitget)
CRYPTO_OTHERSmaller crypto exchanges
US_BROKERUS stock brokers (Alpaca, Interactive Brokers)
EU_BROKEREuropean brokers (eToro, DEGIRO)
APAC_BROKERAsia-Pacific brokers (Tiger Brokers, Futu)
CIS_BROKERCIS region brokers (Tinkoff)
KZ_BROKERKazakhstan brokers (KASE, Freedom Finance)
FOREXForex brokers (OANDA, Pepperstone)
FUTURES_DERIVATIVESTraditional futures (CME, ICE)
OTC_INSTITUTIONALOTC/Institutional desks (Wintermute, Cumberland)
MARKET_DATAData-only providers (Polygon.io, Tiingo)
DEXDecentralized exchanges (Uniswap, SushiSwap)

Tips & Best Practices

  1. Start with data-only — implement trades and orderbook streaming first, add order execution later
  2. Use testnet — if the exchange offers a testnet/sandbox environment, use it during development
  3. Handle reconnection — WebSocket connections will drop; implement automatic reconnection with exponential backoff
  4. Normalize symbols — map exchange-specific symbol formats (e.g., BTC_USDT) to Barfinex standard (BTCUSDT)
  5. Rate limiting — respect exchange API rate limits; implement request queuing if needed
  6. Error mapping — map exchange-specific error codes to meaningful error messages
  7. Logging — use NestJS Logger for structured logging that integrates with Provider's log pipeline

Next Steps

Let’s Get in Touch

Have questions or want to explore Barfinex? Send us a message.