260 lines
9.1 KiB
JavaScript
260 lines
9.1 KiB
JavaScript
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]
|
|
}
|