2023-02-25 16:22:39 -08:00
|
|
|
/* 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'
|
2023-02-15 14:17:58 -08:00
|
|
|
import {
|
|
|
|
closeSocket,
|
2023-03-04 12:42:35 -08:00
|
|
|
// isOpen,
|
|
|
|
subscribeOnStream as subscribeOnSpotStream,
|
2023-02-15 14:17:58 -08:00
|
|
|
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()
|
|
|
|
|
2023-03-10 07:49:06 -08:00
|
|
|
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[] = []
|
2023-03-06 12:15:21 -08:00
|
|
|
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,
|
2023-03-06 12:15:21 -08:00
|
|
|
open: previousBar ? previousBar.close : bar.open,
|
2023-03-04 12:42:35 -08:00
|
|
|
close: bar.close,
|
|
|
|
volume: bar.volume,
|
|
|
|
timestamp,
|
|
|
|
},
|
|
|
|
]
|
2023-03-06 12:15:21 -08:00
|
|
|
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-02-08 17:40:34 -08:00
|
|
|
|
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-02-15 14:17:58 -08:00
|
|
|
|
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,
|
|
|
|
})
|
2023-02-15 14:17:58 -08:00
|
|
|
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
|
|
|
) => {
|
2023-03-10 07:49:06 -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
|
|
|
},
|
|
|
|
|
2023-03-10 07:49:06 -08:00
|
|
|
unsubscribeBars: (subscriberUID: string) => {
|
2023-03-04 12:42:35 -08:00
|
|
|
if (marketType === 'perp') {
|
2023-03-10 07:49:06 -08:00
|
|
|
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
|
|
|
|
2023-02-15 14:17:58 -08:00
|
|
|
closeSocket: () => {
|
2023-03-04 12:42:35 -08:00
|
|
|
if (marketType === 'spot') {
|
|
|
|
closeSocket()
|
|
|
|
} else {
|
|
|
|
closePerpSocket()
|
|
|
|
}
|
2023-02-15 14:17:58 -08:00
|
|
|
},
|
2023-03-04 12:42:35 -08:00
|
|
|
|
2023-02-15 14:17:58 -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
|
|
|
}
|