import {useStore} from "@/store/store.js"; import {ohlcStart} from "@/charts/chart-misc.js"; // support for Dexorder OHLC data files function dailyFile(resName) { function _filename(symbol, timestamp) { const date = new Date(timestamp*1000) const year = date.getUTCFullYear() const month = ('0'+(date.getUTCMonth() + 1)).slice(-2) const day = ('0'+date.getUTCDate()).slice(-2) return `${resName}/${year}/${month}/${symbol}-${resName}-${year}${month}${day}.csv` } return _filename } function monthlyFile(resName) { function _filename(symbol, timestamp) { const date = new Date(timestamp*1000) const year = date.getUTCFullYear() const month = ('0'+(date.getUTCMonth() + 1)).slice(-2) return `${resName}/${year}/${symbol}-${resName}-${year}${month}.csv` } return _filename } function yearlyFile(resName) { function _filename(symbol, timestamp) { const date = new Date(timestamp*1000) const year = date.getUTCFullYear() return `${resName}/${symbol}-${resName}-${year}.csv` } return _filename } function singleFile(resName) { function _filename(symbol, timestamp) { return `${resName}/${symbol}-${resName}.csv` } return _filename } function nextDay(timestamp) { const date = new Date(timestamp*1000) return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1) / 1000 } function nextMonth(timestamp) { const date = new Date(timestamp*1000) return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000 } function nextYear(timestamp) { const date = new Date(timestamp*1000) return Date.UTC(date.getUTCFullYear() + 1, date.getUTCMonth(), date.getUTCDate()) / 1000 } function never(_timestamp) { return Number.MAX_SAFE_INTEGER } // noinspection PointlessArithmeticExpressionJS const resolutions = [ { seconds: 1 * 60, name: '1m', tvRes: '1', filename: dailyFile( '1m'), nextStart: nextDay, }, { seconds: 3 * 60, name: '3m', tvRes: '3', filename: dailyFile( '3m'), nextStart: nextDay, }, { seconds: 5 * 60, name: '5m', tvRes: '5', filename: dailyFile( '5m'), nextStart: nextDay, }, { seconds: 10 * 60, name: '10m', tvRes: '10', filename: dailyFile('10m'), nextStart: nextDay, }, { seconds: 15 * 60, name: '15m', tvRes: '15', filename: dailyFile('15m'), nextStart: nextDay, }, { seconds: 30 * 60, name: '30m', tvRes: '30', filename: dailyFile('30m'), nextStart: nextDay, }, { seconds: 60 * 60, name: '1H', tvRes: '60', filename: monthlyFile( '1H'), nextStart: nextMonth, }, { seconds: 120 * 60, name: '2H', tvRes: '120', filename: monthlyFile( '2H'), nextStart: nextMonth, }, { seconds: 240 * 60, name: '4H', tvRes: '240', filename: monthlyFile( '4H'), nextStart: nextMonth, }, { seconds: 480 * 60, name: '8H', tvRes: '480', filename: monthlyFile( '8H'), nextStart: nextMonth, }, { seconds: 720 * 60, name: '12H', tvRes: '720', filename: monthlyFile('12H'), nextStart: nextMonth, }, { seconds: 1440 * 60, name: '1D', tvRes: '1D', filename: yearlyFile( '1D'), nextStart: nextYear, }, { seconds: 2880 * 60, name: '2D', tvRes: '2D', filename: yearlyFile( '2D'), nextStart: nextYear, }, { seconds: 4320 * 60, name: '3D', tvRes: '3D', filename: yearlyFile( '3D'), nextStart: nextYear, }, { seconds: 10080 * 60, name: '1W', tvRes: '1W', filename: singleFile( '1W'), nextStart: never, }, ] const tvResMap = {} for (const res of resolutions) tvResMap[res.tvRes] = res const dxoResMap = {} for (const res of resolutions) dxoResMap[res.name] = res const seriesStarts = {} async function getUrl(url) { try { const response = await fetch(url) // console.log('got response', response) if (response.ok) return await response.text() else console.error(`could not fetch ${url}: status ${response.statusText}`) } catch (e) { console.error(`Could not fetch ${url}`, e) } return '' } export async function loadOHLC (symbol, contract, from, to, tvRes) { // console.log('loadOHLC', tvRes, new Date(1000*from), new Date(1000*to), symbol, contract); let chainId let bars = []; let inverted = false; let baseUrl let latest = null // latest time, price function fill(end, period) { if (latest===null) return const [latestTime, price] = latest end = ohlcStart(end, period) const start = ohlcStart(latestTime+period, period); for (let now= start; now < end; now += period ) { bars.push({time:now * 1000, open:price, high:price, low:price, close:price}) latest = [now, price] } } if (symbol.x?.data) { baseUrl = symbol.x.data.uri contract = symbol.x.data.symbol chainId = symbol.x.data.chain inverted ^= symbol.x.data.inverted } else { baseUrl = `/ohlc/` chainId = useStore().chainId } baseUrl += `${chainId}/${contract}/` const res = tvResMap[tvRes] const fetches = [] let start = from if (!(baseUrl in seriesStarts)) { try { // console.log('getting quote', baseUrl+'quote.csv') const response = await getUrl(baseUrl+'quote.csv') if (response.length) { seriesStarts[baseUrl] = parseInt(response.split(',')[0]) // console.log(`Series ${baseUrl} starts at ${new Date(start*1000)}`) } else { console.error(`Bad response while fetching ${baseUrl+'quote.csv'}`) } } catch (e) { console.error(e) } } if (baseUrl in seriesStarts) start = Math.max(start, seriesStarts[baseUrl]) for(let now = start; now < to; now = res.nextStart(now)) { const url = baseUrl + res.filename(contract, now); const prom = getUrl(url) fetches.push(prom); } const responses = await Promise.all(fetches) for (const response of responses) { if (response.length) { let lineNum = 0 response.split('\n').forEach((line) => { lineNum++ const row = line.split(',') let time, open, high, low, close=null switch (row.length) { case 1: if (row[0].length !== 0) console.log(`Warning: weird nonempty row at OHLC line ${lineNum}: "${line}"`) break case 2: time = parseInt(row[0]) if (time < start || time >= to) break let price = parseFloat(row[1]) if (inverted) price = 1/price open = latest === null ? price : latest[1] high = low = close = price break case 3: time = parseInt(row[0]) if (time < start || time >= to) break open = parseFloat(row[1]) close = parseFloat(row[2]) if (inverted) { open = 1/open close = 1/close } high = Math.max(open, close) low = Math.min(open,close) if (latest!==null) open = latest[1] break case 5: time = parseInt(row[0]) if (time < start || time >= to) break open = parseFloat(row[1]) high = parseFloat(row[2]) low = parseFloat(row[3]) close = parseFloat(row[4]) if (inverted) { open = 1/open const h = high high = 1/low low = 1/h close = 1/close } break default: console.log(`Warning: could not parse line ${lineNum} of OHLC file:\n${line}`) break } if (close!==null) { fill(time, res.seconds) const bar = {time:time*1000, open, high, low, close}; bars.push(bar) latest = [time, close] } }) // console.log(`processed ${lineNum} lines`) } // else { console.log('response was empty') } } fill(to, res.seconds) return bars } export function tvResolutionToPeriodString(res) { return tvResMap[res].name } export function convertTvResolution(res) { return tvResMap[res] } export function resForName(name) { return dxoResMap[name] }