mango-v4-ui/apis/datafeed.ts

353 lines
8.2 KiB
TypeScript
Raw Normal View History

/* eslint-disable @typescript-eslint/no-explicit-any */
2023-03-04 21:48:09 -08:00
import { makeApiRequest, parseResolution } from './birdeye/helpers'
2023-03-04 12:42:35 -08:00
import {
makeApiRequest as makePerpApiRequest,
parseResolution as parsePerpResolution,
2023-03-04 21:48:09 -08:00
} from './mngo/helpers'
import {
closeSocket,
2023-03-04 12:42:35 -08:00
// isOpen,
subscribeOnStream as subscribeOnSpotStream,
unsubscribeFromStream,
2023-03-04 21:48:09 -08:00
} from './birdeye/streaming'
2023-03-04 12:42:35 -08:00
import {
closeSocket as closePerpSocket,
// isOpen as isPerpOpen,
subscribeOnStream as subscribeOnPerpStream,
unsubscribeFromStream as unsubscribeFromPerpStream,
2023-03-04 21:48:09 -08:00
} from './mngo/streaming'
2022-12-19 17:09:33 -08:00
import mangoStore from '@store/mangoStore'
2023-01-02 05:30:12 -08:00
import {
2023-01-02 05:41:22 -08:00
DatafeedConfiguration,
2023-01-02 05:30:12 -08:00
LibrarySymbolInfo,
ResolutionString,
2023-01-02 05:41:22 -08:00
SearchSymbolResultItem,
2023-01-28 11:21:45 -08:00
} from '@public/charting_library'
2023-01-02 05:30:12 -08:00
2023-01-02 11:50:03 -08:00
export const SUPPORTED_RESOLUTIONS = [
'1',
'3',
'5',
'15',
'30',
'60',
'120',
'240',
'1D',
] as const
type BaseBar = {
2023-01-02 05:41:22 -08:00
low: number
high: number
open: number
close: number
}
2023-01-02 11:50:03 -08:00
type KlineBar = BaseBar & {
volume: number
timestamp: number
}
type TradingViewBar = BaseBar & {
time: number
}
type Bar = KlineBar & TradingViewBar
2023-02-27 23:20:11 -08:00
export type SymbolInfo = LibrarySymbolInfo & {
2023-01-02 05:30:12 -08:00
address: string
}
2022-12-15 15:33:31 -08:00
const lastBarsCache = new Map()
const subscriptionIds = new Map()
2022-12-15 15:33:31 -08:00
const configurationData = {
2023-01-02 11:50:03 -08:00
supported_resolutions: SUPPORTED_RESOLUTIONS,
2022-12-15 15:33:31 -08:00
intraday_multipliers: ['1', '3', '5', '15', '30', '60', '120', '240'],
exchanges: [],
}
2022-12-19 17:09:33 -08:00
// async function getAllSymbols() {
// const data = await makeApiRequest(
// 'public/tokenlist?sort_by=v24hUSD&sort_type=desc&offset=0&limit=-1'
// )
2022-12-15 15:33:31 -08:00
2022-12-19 17:09:33 -08:00
// return data.data.tokens
// }
2022-12-15 15:33:31 -08:00
2023-03-04 12:42:35 -08:00
let marketType: 'spot' | 'perp'
export const queryPerpBars = async (
tokenAddress: string,
resolution: typeof SUPPORTED_RESOLUTIONS[number],
periodParams: {
firstDataRequest: boolean
from: number
to: number
}
): Promise<Bar[]> => {
const { from, to } = periodParams
const urlParameters = {
'perp-market': tokenAddress,
resolution: parsePerpResolution(resolution),
start_datetime: new Date(from * 1000).toISOString(),
end_datetime: new Date(to * 1000).toISOString(),
}
const query = Object.keys(urlParameters)
.map((name: string) => `${name}=${(urlParameters as any)[name]}`)
.join('&')
const data = await makePerpApiRequest(`/stats/candles-perp?${query}`)
if (!data || !data.length) {
return []
}
let bars: Bar[] = []
let previousBar: Bar | undefined = undefined
2023-03-04 12:42:35 -08:00
for (const bar of data) {
const timestamp = new Date(bar.candle_start).getTime()
if (timestamp >= from * 1000 && timestamp < to * 1000) {
bars = [
...bars,
{
time: timestamp,
low: bar.low,
high: bar.high,
open: previousBar ? previousBar.close : bar.open,
2023-03-04 12:42:35 -08:00
close: bar.close,
volume: bar.volume,
timestamp,
},
]
previousBar = bar
2023-03-04 12:42:35 -08:00
}
}
return bars
}
export const queryBirdeyeBars = async (
2023-01-02 11:17:27 -08:00
tokenAddress: string,
2023-01-02 11:50:03 -08:00
resolution: typeof SUPPORTED_RESOLUTIONS[number],
2023-01-02 11:17:27 -08:00
periodParams: {
firstDataRequest: boolean
from: number
to: number
}
): Promise<Bar[]> => {
const { from, to } = periodParams
const urlParameters = {
address: tokenAddress,
type: parseResolution(resolution),
time_from: from,
time_to: to,
}
const query = Object.keys(urlParameters)
.map(
(name: string) =>
`${name}=${encodeURIComponent((urlParameters as any)[name])}`
)
.join('&')
const data = await makeApiRequest(`defi/ohlcv/pair?${query}`)
if (!data.success || data.data.items.length === 0) {
return []
}
2023-01-02 11:17:27 -08:00
let bars: Bar[] = []
for (const bar of data.data.items) {
if (bar.unixTime >= from && bar.unixTime < to) {
2023-01-02 11:50:03 -08:00
const timestamp = bar.unixTime * 1000
2023-03-21 11:04:24 -07:00
if (bar.h > 22311) continue
2023-01-02 11:17:27 -08:00
bars = [
...bars,
{
2023-01-02 11:50:03 -08:00
time: timestamp,
2023-01-02 11:17:27 -08:00
low: bar.l,
high: bar.h,
open: bar.o,
close: bar.c,
2023-01-02 11:50:03 -08:00
volume: bar.v,
timestamp,
2023-01-02 11:17:27 -08:00
},
]
}
}
return bars
}
2022-12-15 15:33:31 -08:00
export default {
2023-01-02 05:41:22 -08:00
onReady: (callback: (configuration: DatafeedConfiguration) => void) => {
2023-01-02 11:50:03 -08:00
setTimeout(() => callback(configurationData as any))
2022-12-15 15:33:31 -08:00
},
searchSymbols: async (
2023-01-02 05:41:22 -08:00
_userInput: string,
_exchange: string,
_symbolType: string,
_onResultReadyCallback: (items: SearchSymbolResultItem[]) => void
2022-12-15 15:33:31 -08:00
) => {
return
},
resolveSymbol: async (
2023-01-02 05:30:12 -08:00
symbolAddress: string,
onSymbolResolvedCallback: (symbolInfo: SymbolInfo) => void
// _onResolveErrorCallback: any,
// _extension: any
2022-12-15 15:33:31 -08:00
) => {
2023-01-02 05:30:12 -08:00
let symbolItem:
| {
address: string
type: string
symbol: string
}
| undefined
2022-12-15 15:33:31 -08:00
if (!symbolItem) {
2022-12-15 22:13:00 -08:00
symbolItem = {
address: symbolAddress,
type: 'pair',
2023-01-02 05:30:12 -08:00
symbol: '',
2022-12-15 22:13:00 -08:00
}
2022-12-15 15:33:31 -08:00
}
2022-12-19 17:09:33 -08:00
const ticker = mangoStore.getState().selectedMarket.name
2023-03-04 21:48:09 -08:00
console.log('ticker', ticker, mangoStore.getState().group)
2022-12-15 15:33:31 -08:00
2023-01-02 05:30:12 -08:00
const symbolInfo: SymbolInfo = {
2022-12-15 15:33:31 -08:00
address: symbolItem.address,
ticker: symbolItem.address,
2022-12-15 22:13:00 -08:00
name: symbolItem.symbol || symbolItem.address,
2022-12-19 17:09:33 -08:00
description: ticker || symbolItem.address,
2022-12-15 15:33:31 -08:00
type: symbolItem.type,
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_weekly_and_monthly: false,
2023-02-01 18:56:07 -08:00
has_empty_bars: true,
2023-01-02 11:50:03 -08:00
supported_resolutions: configurationData.supported_resolutions as any,
2022-12-15 15:33:31 -08:00
intraday_multipliers: configurationData.intraday_multipliers,
volume_precision: 2,
data_status: 'streaming',
2023-01-02 05:41:22 -08:00
full_name: '',
exchange: '',
2023-01-02 05:30:12 -08:00
listed_exchange: '',
format: 'price',
2022-12-15 15:33:31 -08:00
}
onSymbolResolvedCallback(symbolInfo)
},
getBars: async (
2023-01-02 05:30:12 -08:00
symbolInfo: SymbolInfo,
2023-01-02 11:50:03 -08:00
resolution: ResolutionString,
2023-01-02 05:30:12 -08:00
periodParams: {
countBack: number
firstDataRequest: boolean
from: number
to: number
},
onHistoryCallback: (
2023-01-02 05:41:22 -08:00
bars: Bar[],
2023-01-02 05:30:12 -08:00
t: {
noData: boolean
}
) => void,
onErrorCallback: (e: any) => void
2022-12-15 15:33:31 -08:00
) => {
try {
2023-01-02 11:17:27 -08:00
const { firstDataRequest } = periodParams
2023-03-04 12:42:35 -08:00
let bars
2023-03-04 12:52:09 -08:00
if (
symbolInfo.description?.includes('PERP') &&
symbolInfo.address !== '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
) {
2023-03-04 12:42:35 -08:00
marketType = 'perp'
bars = await queryPerpBars(
symbolInfo.address,
resolution as any,
periodParams
)
} else {
marketType = 'spot'
bars = await queryBirdeyeBars(
symbolInfo.address,
resolution as any,
periodParams
)
}
2023-01-02 11:17:27 -08:00
if (!bars || bars.length === 0) {
2022-12-15 15:33:31 -08:00
// "noData" should be set if there is no data in the requested period.
onHistoryCallback([], {
noData: true,
})
return
}
if (firstDataRequest) {
lastBarsCache.set(symbolInfo.address, {
...bars[bars.length - 1],
})
}
onHistoryCallback(bars, {
noData: false,
})
return bars
2022-12-15 15:33:31 -08:00
} catch (error) {
2023-01-06 11:41:03 -08:00
console.warn('[getBars]: Get error', error)
2022-12-15 15:33:31 -08:00
onErrorCallback(error)
}
},
subscribeBars: (
2023-01-02 05:41:22 -08:00
symbolInfo: SymbolInfo,
resolution: string,
onRealtimeCallback: (data: any) => void,
subscriberUID: string,
onResetCacheNeededCallback: () => void
2022-12-15 15:33:31 -08:00
) => {
subscriptionIds.set(subscriberUID, symbolInfo.address)
2023-03-04 12:42:35 -08:00
if (symbolInfo.description?.includes('PERP')) {
subscribeOnPerpStream(
symbolInfo,
resolution,
onRealtimeCallback,
subscriberUID,
onResetCacheNeededCallback,
lastBarsCache.get(symbolInfo.address)
)
} else {
subscribeOnSpotStream(
symbolInfo,
resolution,
onRealtimeCallback,
subscriberUID,
onResetCacheNeededCallback,
lastBarsCache.get(symbolInfo.address)
)
}
2022-12-15 15:33:31 -08:00
},
unsubscribeBars: (subscriberUID: string) => {
2023-03-04 12:42:35 -08:00
if (marketType === 'perp') {
const marketId = subscriptionIds.get(subscriberUID)
unsubscribeFromPerpStream(marketId)
2023-03-04 12:42:35 -08:00
} else {
unsubscribeFromStream()
}
2022-12-15 15:33:31 -08:00
},
2023-03-04 12:42:35 -08:00
closeSocket: () => {
2023-03-04 12:42:35 -08:00
if (marketType === 'spot') {
closeSocket()
} else {
closePerpSocket()
}
},
2023-03-04 12:42:35 -08:00
name: 'birdeye',
2023-03-04 12:42:35 -08:00
// isSocketOpen: marketType === 'spot' ? isOpen : isPerpOpen,
2022-12-15 15:33:31 -08:00
}