-
-
Save bradens/bfe449f8ea88fca8a1952cfe242b5e21 to your computer and use it in GitHub Desktop.
Codex Token bars Datafeed Example.
This file contains hidden or 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 an example datafeed.ts for the tradingview advanced charting library. | |
| * It is not intended to be copy/pasted into your project, but rather used as a reference for how to implement the datafeed. | |
| */ | |
| // Import these types from your charting_library folder | |
| import type { | |
| HistoryCallback, | |
| IBasicDataFeed, | |
| LibrarySymbolInfo, | |
| ResolutionString, | |
| ResolveCallback, | |
| SubscribeBarsCallback, | |
| } from "charting_library"; | |
| import { CleanupFunction, Codex } from "@codex-data/sdk"; | |
| const sdk = new Codex(process.env.CODEX_API_KEY || ""); | |
| // Define the bar type TradingView expects | |
| type TradingViewBar = { | |
| time: number; // TradingView expects milliseconds | |
| open: number; | |
| high: number; | |
| low: number; | |
| close: number; | |
| volume?: number; | |
| } | |
| const subs: Record<string, CleanupFunction> = {}; | |
| /** | |
| * | |
| * Call this from an effect whereever you are instantiating the trading view chart | |
| * | |
| * ex: useEffect(() => { | |
| * const datafeed = createTradingViewDatafeed({ | |
| * fetchTokenDetails, | |
| * fetchBars, | |
| * onSubscriptionChange: setCurrentSubscription, | |
| * prefetchedTimeRange, | |
| * }); | |
| * | |
| * | |
| * @returns tradingview datafeed implementation | |
| */ | |
| export const createTradingViewDatafeed = (): IBasicDataFeed => { | |
| return { | |
| // This is used to resolve the symbol for price scales / and names in the top left. | |
| resolveSymbol: async ( | |
| symbolName: string, // This depends on how you are setting the symbol in your charting library, but lets assume it's a tokenId:networkId string, to match Codex IDs | |
| onSymbolResolvedCallback: ResolveCallback | |
| ) => { | |
| // Default/fallback symbol info | |
| let tokenDisplay = symbolName; | |
| let tokenDescription = symbolName; | |
| const [address, networkIdString] = symbolName.split(":"); | |
| const networkId = parseInt(networkIdString); | |
| try { | |
| const data = await sdk.queries.token({ input: { networkId: networkId, address } }); | |
| tokenDisplay = data.token.symbol || data.token.name || symbolName; | |
| tokenDescription = data.token.name || data.token.symbol || symbolName; | |
| } catch (error: unknown) { | |
| console.error("Error resolving symbol", error); | |
| return | |
| } | |
| const symbolInfo: LibrarySymbolInfo = { | |
| ticker: symbolName, // Keep original ticker | |
| name: tokenDisplay, // Use fetched symbol/name | |
| description: tokenDescription, // Use fetched name/symbol | |
| type: "crypto", | |
| session: "24x7", | |
| timezone: "Etc/UTC", | |
| exchange: "Codex", | |
| listed_exchange: "Codex", | |
| minmov: 1, | |
| minmove2: 1, | |
| pricescale: 100000000, // Adjust these values to change the chart axes. | |
| has_intraday: true, | |
| has_weekly_and_monthly: true, | |
| has_seconds: true, | |
| volume_precision: 2, | |
| data_status: "streaming", | |
| supported_resolutions: [ | |
| "1S", | |
| "5S", | |
| "15S", | |
| "30S", | |
| "1", | |
| "5", | |
| "15", | |
| "30", | |
| "60", | |
| "1D", | |
| ] as ResolutionString[], | |
| format: "price", | |
| }; | |
| setTimeout(() => onSymbolResolvedCallback(symbolInfo), 0); | |
| }, | |
| getBars: async ( | |
| symbolInfo: LibrarySymbolInfo, | |
| resolution: ResolutionString, | |
| periodParams: { | |
| from: number; | |
| to: number; | |
| countBack?: number; | |
| }, | |
| onHistoryCallback: HistoryCallback, | |
| _onErrorCallback: (reason: string) => void | |
| ) => { | |
| const { from, to } = periodParams; | |
| const adjustedTo = | |
| to > Math.floor(Date.now() / 1000) ? Math.floor(Date.now() / 1000) : to; | |
| // For the first data request, use server-calculated timestamps if available | |
| // This ensures cache hit with prefetched data | |
| const queryFrom = from; | |
| const queryTo = adjustedTo; | |
| const queryResolution = resolution.toString(); | |
| try { | |
| const data = await sdk.queries.getBars({ | |
| symbol: symbolInfo.ticker, | |
| from: queryFrom, | |
| to: queryTo, | |
| resolution: queryResolution, | |
| removeEmptyBars: true, | |
| removeLeadingNullValues: true, | |
| }); | |
| const barsData = data?.getBars; | |
| let tradingViewBars: TradingViewBar[] = []; | |
| if (barsData?.t) { | |
| tradingViewBars = barsData.t | |
| .map((time: number, index: number) => { | |
| const open = barsData.o?.[index]; | |
| const high = barsData.h?.[index]; | |
| const low = barsData.l?.[index]; | |
| const close = barsData.c?.[index]; | |
| const volume = barsData.volume?.[index]; | |
| if ( | |
| open == null || | |
| high == null || | |
| low == null || | |
| close == null | |
| ) { | |
| return null; | |
| } | |
| // Create the bar object explicitly matching TradingViewBar | |
| const bar: TradingViewBar = { | |
| time: time * 1000, | |
| open: open, | |
| high: high, | |
| low: low, | |
| close: close, | |
| }; | |
| // Add volume only if it exists, convert string to number | |
| if (volume != null) { | |
| bar.volume = parseFloat(volume); | |
| } | |
| return bar; | |
| }) | |
| .filter(Boolean) as TradingViewBar[]; // Add type assertion | |
| onHistoryCallback(tradingViewBars, { | |
| noData: tradingViewBars.length === 0, | |
| }); | |
| } else { | |
| onHistoryCallback([], { noData: true }); | |
| } | |
| } catch (error) { | |
| _onErrorCallback(error instanceof Error ? error.message : "Unknown error"); | |
| } | |
| }, | |
| subscribeBars: async ( | |
| symbolInfo: LibrarySymbolInfo, | |
| resolution: ResolutionString, | |
| onRealtimeCallback: SubscribeBarsCallback, | |
| subscriberUID: string | |
| ) => { | |
| const [address, networkIdString] = symbolInfo.ticker.split(":"); | |
| const networkId = parseInt(networkIdString); | |
| subs[subscriberUID] = await sdk.subscriptions.onTokenBarsUpdated({ | |
| tokenId: address, | |
| networkId: networkId, | |
| }, { | |
| next: (value) => { | |
| // FIXME: Currently this just reads from the 1m resolution, and reading from multiple resolutions is left out for brevity. | |
| const bar: TradingViewBar = { | |
| time: value.data?.onTokenBarsUpdated?.timestamp ?? 0 * 1000, // TradingView expects milliseconds | |
| open: value.data?.onTokenBarsUpdated?.aggregates?.r1?.usd?.o ?? 0, | |
| high: value.data?.onTokenBarsUpdated?.aggregates?.r1?.usd?.h ?? 0, | |
| low: value.data?.onTokenBarsUpdated?.aggregates?.r1?.usd?.l ?? 0, | |
| close: value.data?.onTokenBarsUpdated?.aggregates?.r1?.usd?.c ?? 0, | |
| volume: value.data?.onTokenBarsUpdated?.aggregates?.r1?.usd?.volume ? parseFloat(value.data?.onTokenBarsUpdated?.aggregates?.r1?.usd?.volume) : undefined, | |
| } | |
| onRealtimeCallback(bar) | |
| }, | |
| error: (error) => { | |
| console.error("Error subscribing to token bars", error); | |
| }, | |
| complete: () => console.error("Subscription completed"), | |
| }); | |
| }, | |
| unsubscribeBars: (_subscriberUID: string) => { | |
| subs[_subscriberUID](); | |
| }, | |
| }; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment