-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add bitget and bitget-futures support
- Loading branch information
Showing
8 changed files
with
733 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
import { upperCaseSymbols } from '../handy' | ||
import { BookChange, BookTicker, DerivativeTicker, Exchange, Trade } from '../types' | ||
import { Mapper, PendingTickerInfoHelper } from './mapper' | ||
|
||
export class BitgetTradesMapper implements Mapper<'bitget' | 'bitget-futures', Trade> { | ||
constructor(private readonly _exchange: Exchange) {} | ||
|
||
canHandle(message: BitgetTradeMessage) { | ||
return message.arg.channel === 'trade' && message.action === 'update' | ||
} | ||
|
||
getFilters(symbols?: string[]) { | ||
symbols = upperCaseSymbols(symbols) | ||
|
||
return [ | ||
{ | ||
channel: 'trade', | ||
symbols | ||
} as const | ||
] | ||
} | ||
|
||
*map(message: BitgetTradeMessage, localTimestamp: Date): IterableIterator<Trade> { | ||
for (let trade of message.data) { | ||
yield { | ||
type: 'trade', | ||
symbol: message.arg.instId, | ||
exchange: this._exchange, | ||
id: trade.tradeId, | ||
price: Number(trade.price), | ||
amount: Number(trade.size), | ||
side: trade.side === 'buy' ? 'buy' : 'sell', | ||
timestamp: new Date(Number(trade.ts)), | ||
localTimestamp: localTimestamp | ||
} | ||
} | ||
} | ||
} | ||
|
||
function mapPriceLevel(level: [string, string]) { | ||
return { | ||
price: Number(level[0]), | ||
amount: Number(level[1]) | ||
} | ||
} | ||
export class BitgetBookChangeMapper implements Mapper<'bitget' | 'bitget-futures', BookChange> { | ||
constructor(private readonly _exchange: Exchange) {} | ||
|
||
canHandle(message: BitgetOrderbookMessage) { | ||
return message.arg.channel === 'books' && (message.action === 'update' || message.action === 'snapshot') | ||
} | ||
|
||
getFilters(symbols?: string[]) { | ||
symbols = upperCaseSymbols(symbols) | ||
|
||
return [ | ||
{ | ||
channel: 'books', | ||
symbols | ||
} as const | ||
] | ||
} | ||
|
||
*map(message: BitgetOrderbookMessage, localTimestamp: Date): IterableIterator<BookChange> { | ||
for (let orderbookData of message.data) { | ||
yield { | ||
type: 'book_change', | ||
symbol: message.arg.instId, | ||
exchange: this._exchange, | ||
isSnapshot: message.action === 'snapshot', | ||
bids: orderbookData.bids.map(mapPriceLevel), | ||
asks: orderbookData.asks.map(mapPriceLevel), | ||
timestamp: new Date(Number(orderbookData.ts)), | ||
localTimestamp | ||
} | ||
} | ||
} | ||
} | ||
|
||
export class BitgetBookTickerMapper implements Mapper<'bitget' | 'bitget-futures', BookTicker> { | ||
constructor(private readonly _exchange: Exchange) {} | ||
|
||
canHandle(message: BitgetBBoMessage) { | ||
return message.arg.channel === 'books1' && message.action === 'snapshot' | ||
} | ||
|
||
getFilters(symbols?: string[]) { | ||
symbols = upperCaseSymbols(symbols) | ||
|
||
return [ | ||
{ | ||
channel: `books1` as const, | ||
symbols | ||
} | ||
] | ||
} | ||
|
||
*map(message: BitgetBBoMessage, localTimestamp: Date): IterableIterator<BookTicker> { | ||
for (const bboMessage of message.data) { | ||
const ticker: BookTicker = { | ||
type: 'book_ticker', | ||
symbol: message.arg.instId, | ||
exchange: this._exchange, | ||
|
||
askAmount: Number(bboMessage.asks[0][1]), | ||
askPrice: Number(bboMessage.asks[0][0]), | ||
|
||
bidPrice: Number(bboMessage.bids[0][0]), | ||
bidAmount: Number(bboMessage.bids[0][1]), | ||
timestamp: new Date(Number(bboMessage.ts)), | ||
localTimestamp: localTimestamp | ||
} | ||
|
||
yield ticker | ||
} | ||
} | ||
} | ||
|
||
export class BitgetDerivativeTickerMapper implements Mapper<'bitget-futures', DerivativeTicker> { | ||
private readonly pendingTickerInfoHelper = new PendingTickerInfoHelper() | ||
|
||
canHandle(message: BitgetTickerMessage) { | ||
return message.arg.channel === 'ticker' && message.action === 'snapshot' | ||
} | ||
|
||
getFilters(symbols?: string[]) { | ||
return [ | ||
{ | ||
channel: 'ticker', | ||
symbols | ||
} as const | ||
] | ||
} | ||
|
||
*map(message: BitgetTickerMessage, localTimestamp: Date): IterableIterator<DerivativeTicker> { | ||
for (const tickerMessage of message.data) { | ||
const pendingTickerInfo = this.pendingTickerInfoHelper.getPendingTickerInfo(tickerMessage.symbol, 'bitget-futures') | ||
|
||
pendingTickerInfo.updateIndexPrice(Number(tickerMessage.indexPrice)) | ||
pendingTickerInfo.updateMarkPrice(Number(tickerMessage.markPrice)) | ||
pendingTickerInfo.updateOpenInterest(Number(tickerMessage.holdingAmount)) | ||
pendingTickerInfo.updateLastPrice(Number(tickerMessage.lastPr)) | ||
|
||
pendingTickerInfo.updateTimestamp(new Date(Number(tickerMessage.ts))) | ||
|
||
if (tickerMessage.nextFundingTime !== '0') { | ||
pendingTickerInfo.updateFundingTimestamp(new Date(Number(tickerMessage.nextFundingTime))) | ||
pendingTickerInfo.updateFundingRate(Number(tickerMessage.fundingRate)) | ||
} | ||
|
||
if (pendingTickerInfo.hasChanged()) { | ||
yield pendingTickerInfo.getSnapshot(localTimestamp) | ||
} | ||
} | ||
} | ||
} | ||
|
||
type BitgetTradeMessage = { | ||
action: 'update' | ||
arg: { instType: 'SPOT'; channel: 'trade'; instId: 'OPUSDT' } | ||
data: [{ ts: '1730332800983'; price: '1.717'; size: '56.16'; side: 'buy'; tradeId: '1235670816495050754' }] | ||
ts: 1730332800989 | ||
} | ||
|
||
type BitgetOrderbookMessage = | ||
| { | ||
action: 'snapshot' | ||
arg: { instType: 'SPOT'; channel: 'books'; instId: 'SYLOUSDT' } | ||
data: [ | ||
{ | ||
asks: [string, string][] | ||
bids: [string, string][] | ||
checksum: 0 | ||
ts: '1730331046984' | ||
} | ||
] | ||
ts: 1730332800437 | ||
} | ||
| { | ||
action: 'update' | ||
arg: { instType: 'SPOT'; channel: 'books'; instId: 'BANDUSDT' } | ||
data: [ | ||
{ | ||
asks: [string, string][] | ||
bids: [string, string][] | ||
checksum: 79466786 | ||
ts: '1730332859977' | ||
} | ||
] | ||
ts: 1730332859979 | ||
} | ||
|
||
type BitgetBBoMessage = { | ||
action: 'snapshot' | ||
arg: { instType: 'SPOT'; channel: 'books1'; instId: 'METISUSDT' } | ||
data: [{ asks: [['44.90', '0.6927']]; bids: [['44.82', '3.5344']]; checksum: 0; ts: '1730332859988' }] | ||
ts: 1730332859989 | ||
} | ||
|
||
type BitgetTickerMessage = { | ||
action: 'snapshot' | ||
arg: { instType: 'COIN-FUTURES'; channel: 'ticker'; instId: 'BTCUSD' } | ||
data: [ | ||
{ | ||
instId: 'BTCUSD' | ||
lastPr: '72331.5' | ||
bidPr: '72331.5' | ||
askPr: '72331.8' | ||
bidSz: '7.296' | ||
askSz: '0.02' | ||
open24h: '72047.8' | ||
high24h: '72934.8' | ||
low24h: '71422.8' | ||
change24h: '-0.00561' | ||
fundingRate: '0.000116' | ||
nextFundingTime: string | ||
markPrice: string | ||
indexPrice: string | ||
holdingAmount: string | ||
baseVolume: '7543.376' | ||
quoteVolume: '544799876.924' | ||
openUtc: '72335.3' | ||
symbolType: '1' | ||
symbol: 'BTCUSD' | ||
deliveryPrice: '0' | ||
ts: '1730332823217' | ||
} | ||
] | ||
ts: 1730332823220 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { batchObjects } from '../handy' | ||
import { Filter } from '../types' | ||
import { RealTimeFeedBase } from './realtimefeed' | ||
|
||
abstract class BitgetRealTimeFeedBase extends RealTimeFeedBase { | ||
protected throttleSubscribeMS = 100 | ||
protected readonly wssURL = 'wss://ws.bitget.com/v2/ws/public' | ||
|
||
protected mapToSubscribeMessages(filters: Filter<string>[]): any[] { | ||
const argsInputs = filters.flatMap((filter) => { | ||
if (!filter.symbols || filter.symbols.length === 0) { | ||
throw new Error('BitgetRealTimeFeed requires explicitly specified symbols when subscribing to live feed') | ||
} | ||
|
||
return filter.symbols.map((symbol) => { | ||
return { | ||
instType: this.getInstType(symbol), | ||
channel: filter.channel, | ||
instId: symbol | ||
} | ||
}) | ||
}) | ||
|
||
const payload = [...batchObjects(argsInputs, 5)].map((args) => { | ||
return { | ||
op: 'subscribe', | ||
args | ||
} | ||
}) | ||
|
||
return payload | ||
} | ||
|
||
protected messageIsError(message: any): boolean { | ||
return message.event === 'error' | ||
} | ||
|
||
abstract getInstType(symbol: string): string | ||
} | ||
|
||
export class BitgetRealTimeFeed extends BitgetRealTimeFeedBase { | ||
getInstType(_: string) { | ||
return 'SPOT' | ||
} | ||
} | ||
|
||
export class BitgetFuturesRealTimeFeed extends BitgetRealTimeFeedBase { | ||
getInstType(symbol: string) { | ||
if (symbol.endsWith('USDT')) { | ||
return 'USDT-FUTURES' | ||
} | ||
|
||
if (symbol.endsWith('PERP')) { | ||
return 'USDC-FUTURES' | ||
} | ||
|
||
return 'COIN-FUTURES' | ||
} | ||
} |
Oops, something went wrong.