diff --git a/src/charts/chart.js b/src/charts/chart.js index d79fb48..d92dc0e 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -30,7 +30,7 @@ export function initWidget(el) { // debug: true, autosize: true, symbol: 'AAPL', - // symbol: 'Bitfinex:BTC/USD', // use this for ohlc + // symbol: 'Uniswap:WETH/USD', // use this for ohlc interval: '1D', container: el, datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"), diff --git a/src/charts/datafeed.js b/src/charts/datafeed.js index d968c0f..de304c2 100644 --- a/src/charts/datafeed.js +++ b/src/charts/datafeed.js @@ -1,12 +1,3 @@ -// import { -// makeApiRequest, -// generateSymbol, -// parseFullSymbol, -// } from './helpers.js'; -// import { -// subscribeOnStream, -// unsubscribeFromStream, -// } from './streaming.js'; import {jBars} from './jBars.js'; const lastBarsCache = new Map(); @@ -14,14 +5,14 @@ const lastBarsCache = new Map(); // DatafeedConfiguration implementation const configurationData = { // Represents the resolutions for bars supported by your datafeed - // supported_resolutions: ['1D', '1W', '1M'], - supported_resolutions: ['1D'], - + supported_resolutions: + ['1', '3', '5', '10', '15', '30', '60', '120', '240', '480', '720', '1D', '2D', '3D', '1W'], + // The `exchanges` arguments are used for the `searchSymbols` method if a user selects the exchange exchanges: [{ - value: 'Bitfinex', - name: 'Bitfinex', - desc: 'Bitfinex', + value: 'Uniswap', + name: 'Uniswap', + desc: 'Uniswap', }, ], // The `symbols_types` arguments are used for the `searchSymbols` method if a user selects this symbol type @@ -34,13 +25,11 @@ const configurationData = { // Obtains all symbols for all exchanges supported by CryptoCompare API async function getAllSymbols() { - // const data = await makeApiRequest('data/v3/all/exchanges'); - // let allSymbols = []; return [{ - symbol: 'BTC/USD', - full_name: 'Bitfinex:BTC/USD', - description: 'BTC/USD', - exchange: 'Bitfinex', + symbol: 'WETH/USD', + full_name: 'Uniswap:WETH/USD', + description: 'WETH/USD 42161/0xC31E54c7a869B9Fc', + exchange: 'Uniswap', type: 'crypto'} ]; } @@ -96,9 +85,9 @@ export default { exchange: symbolItem.exchange, minmov: 1, pricescale: 100, - has_intraday: false, + has_intraday: true, // Added to allow less than one day to work has_no_volume: true, - has_weekly_and_monthly: false, + has_weekly_and_monthly: true, // Added to allow greater than one day to work supported_resolutions: configurationData.supported_resolutions, volume_precision: 2, data_status: 'streaming', @@ -112,17 +101,8 @@ export default { const { from, to, firstDataRequest } = periodParams; console.log('[getBars]: Method call', symbolInfo, resolution, from, to); - // const parsedSymbol = parseFullSymbol(symbolInfo.full_name); - // const urlParameters = { - // e: parsedSymbol.exchange, - // fsym: parsedSymbol.fromSymbol, - // tsym: parsedSymbol.toSymbol, - // toTs: to, - // limit: 2000, - // }; - try { - var bars = await jBars(from, to, resolution); + var bars = await jBars(from, to, resolution); // This is the one that does all the work if (firstDataRequest) { lastBarsCache.set(symbolInfo.full_name, { @@ -147,20 +127,9 @@ export default { onResetCacheNeededCallback, ) => { console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID); - // throw Error('subscribeBars unimplemented'); - // subscribeOnStream( - // symbolInfo, - // resolution, - // onRealtimeCallback, - // subscriberUID, - // onResetCacheNeededCallback, - // lastBarsCache.get(symbolInfo.full_name), - // ); }, unsubscribeBars: (subscriberUID) => { console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID); - // throw Error('unsubscribeBars unimplemented'); - // unsubscribeFromStream(subscriberUID); }, }; diff --git a/src/charts/jBars.js b/src/charts/jBars.js index fbbaae3..28f901d 100644 --- a/src/charts/jBars.js +++ b/src/charts/jBars.js @@ -10,97 +10,136 @@ export async function jBars (from, to, res) { const contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443"; // check parameters - - if (res != "1D") throw Error("Only 1D resolution currently supported"); - - console.assert(fromDate.getUTCHours() == 0, "hours should be zero"); - console.assert(fromDate.getUTCMinutes() == 0, "minutes should be zero"); - console.assert(fromDate.getUTCSeconds() == 0, "seconds should be zero"); - console.assert(fromDate.getUTCMilliseconds() == 0, "milliseconds should be zero"); + // console.assert(fromDate.getUTCHours() == 0, "hours should be zero"); + // console.assert(fromDate.getUTCMinutes() == 0, "minutes should be zero"); + // console.assert(fromDate.getUTCSeconds() == 0, "seconds should be zero"); + // console.assert(fromDate.getUTCMilliseconds() == 0, "milliseconds should be zero"); // Spoof data - var spoof; - { - const yr = "2022"; - const mo = "01"; - const url = `/ohlc/42161/${contract}/${res}/${yr}/${contract}-${res}-${yr}${mo}.json` - const response = await fetch(url); - spoof = await response.json(); - } + // var spoof; + // { + // const yr = "2022"; + // const mo = "01"; + // const url = `/ohlc/42161/${contract}/${res}/${yr}/${contract}-${res}-${yr}${mo}.json` + // const response = await fetch(url); + // spoof = await response.json(); + // } + + const file_res = ['1m', '3m', '5m', '10m', '15m', '30m', '1H', '2H', '4H', '8H', '12H', '1D', '2D', '3D', '1W',]; + const supported_res = ['1', '3', '5', '10', '15', '30', '60', '120', '240', '480', '720', '1D', '2D', '3D', '1W',]; + const daily_res = ['1', '3', '5', '10', '15', '30']; + const single_res = ['1W']; + + if (!supported_res.includes(res)) throw Error(`resolution ${res} not supported.`); + + const is_daily_res = daily_res.includes(res); + const is_single_res = single_res.includes(res); + const is_monthly_res = !is_single_res && !is_daily_res; var bars = []; for ( // Once around for each sample in from-to range let iDate = fromDate, // loop state - iMonth = -1, - iolhc = 0, + iFile = undefined, + iohlc = 0, ohlc; iDate < toDate; - iDate.setUTCDate(iDate.getUTCDate()+1) ) { let bar = undefined; // Fetch one sample file as needed + + if (iFile == undefined ? true : + is_monthly_res ? iFile.getUTCMonth() != iDate.getUTCMonth() : + is_daily_res ? iFile.getUTCDate() != iDate.getUTCDate() : + false // is_single_res + ) { - if (iMonth != iDate.getUTCMonth()) { + const fres = file_res[supported_res.indexOf(res)] const yr = iDate.getUTCFullYear(); - const mo = String(iDate.getUTCMonth()+1).padStart(2, '0'); - const url = `/ohlc/42161/${contract}/${res}/${yr}/${contract}-${res}-${yr}${mo}.json` + const yrdir = is_single_res ? "" : `/${yr}`; + const mo = String(iDate.getUTCMonth()+1).padStart(2, '0'); // January is month 0 in Date object + const date = is_daily_res ? String(iDate.getUTCDate()).padStart(2, '0') : ""; + const yrmo = !is_single_res ? `-${yr}${mo}` : ""; + + let url = `/ohlc/42161/${contract}/${fres}${yrdir}/${contract}-${fres}${yrmo}${date}.json`; + let response = await fetch(url); if (response.ok) { ohlc = await response.json(); } else { ohlc = []; // no file, then empty data } - iMonth = iDate.getUTCMonth(); - iolhc = 0; + iFile = new Date(iDate); + iohlc = 0; } - let ohlcDate = iolhc >= ohlc.length ? undefined : new Date(ohlc[iolhc][0]+'Z'); + // Skip samples not for our time - // no ohlc sample, insert a visible sample + for(; iohlc < ohlc.length; iohlc++ ) { + if ( new Date(ohlc[iohlc][0]+'Z').getTime() >= iDate.getTime() ) break; + } + let ohlcDate = iohlc >= ohlc.length ? undefined : new Date(ohlc[iohlc][0]+'Z'); + + // no ohlc sample file, insert missing sample + + const visible_missing_samples = true; if (ohlcDate == undefined) { bar = { time: iDate.getTime(), - open: 50, - high: 50, - low: 0, - close: 0, } + if (visible_missing_samples) bar = Object.assign(bar, { + // Comment these to make invisible + open: 50, + high: 50, + low: 0, + close: 0, + }); } - // ohlc sample not for this time, insert invisible sample + // file exists, but ohlc sample not for this time, insert missing sample else if ( iDate.getTime() != ohlcDate.getTime() ) { bar = { time: iDate.getTime(), - // open: 100, - // high: 100, - // low: 0, - // close: 0, } + if (visible_missing_samples) bar = Object.assign(bar, { + open: 100, + high: 100, + low: 0, + close: 0, + }); // Copy ohlc sample } else { bar = { time: iDate.getTime(), - open: ohlc[iolhc][1], - high: ohlc[iolhc][2], - low: ohlc[iolhc][3], - close: ohlc[iolhc][4], + open: ohlc[iohlc][1] ?? ohlc[iohlc][4], // open + high: ohlc[iohlc][2] ?? ohlc[iohlc][4], // high + low: ohlc[iohlc][3] ?? ohlc[iohlc][4], // low + close: ohlc[iohlc][4], // close } - iolhc++; + iohlc++; } - if (bar==undefined) throw "bar==undefined"; - bars.push(bar); + if (bar != undefined) bars.push(bar); + // Increment loop variable + + if (supported_res.indexOf(res)1day + iDate.setUTCDate(iDate.getUTCDate()+1); + } } return bars;