OHLC load fixes; package updates
This commit is contained in:
@@ -23,14 +23,11 @@ export function removeSymbolChangedCallback(cb) {
|
||||
}
|
||||
|
||||
function symbolChanged(symbol) {
|
||||
if (symbol===null)
|
||||
co.selectedSymbol = null
|
||||
else {
|
||||
const info = lookupSymbol(symbol.ticker)
|
||||
symbolChangedCbs.forEach((cb) => cb(info))
|
||||
co.selectedSymbol = info
|
||||
}
|
||||
const info = symbol===null ? null : lookupSymbol(symbol.ticker)
|
||||
co.selectedSymbol = info
|
||||
symbolChangedCbs.forEach((cb) => cb(info))
|
||||
updateFeeDropdown()
|
||||
console.log('symbol changed', info)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ export const DataFeed = {
|
||||
|
||||
async getBars(symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) {
|
||||
const { from, to, firstDataRequest } = periodParams;
|
||||
log('[getBars]: Method call', symbolInfo, resolution, from, to);
|
||||
log('[getBars]: Method call', symbolInfo, resolution, new Date(from*1000), new Date(to*1000));
|
||||
try {
|
||||
// todo need to consider the selected fee tier
|
||||
await getAllSymbols()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
import {LRUCache} from "lru-cache";
|
||||
|
||||
|
||||
// support for Dexorder OHLC data files
|
||||
@@ -43,19 +44,36 @@ function singleFile(resName) {
|
||||
}
|
||||
|
||||
|
||||
function nextDay(timestamp) {
|
||||
function addDay(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1) / 1000
|
||||
}
|
||||
|
||||
function addMonth(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
const result = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
|
||||
console.log('addMonth', timestamp, result, new Date(timestamp*1000), new Date(result*1000))
|
||||
return result
|
||||
}
|
||||
|
||||
function addYear(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear() + 1, date.getUTCMonth(), date.getUTCDate()) / 1000
|
||||
}
|
||||
|
||||
function nextDay(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + 1, 0, 0, 0) / 1000
|
||||
}
|
||||
|
||||
function nextMonth(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) / 1000
|
||||
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1, 0, 0, 0) / 1000
|
||||
}
|
||||
|
||||
function nextYear(timestamp) {
|
||||
const date = new Date(timestamp*1000)
|
||||
return Date.UTC(date.getUTCFullYear() + 1, date.getUTCMonth(), date.getUTCDate()) / 1000
|
||||
return Date.UTC(date.getUTCFullYear() + 1, 1, 1, 0, 0, 0) / 1000
|
||||
}
|
||||
|
||||
|
||||
@@ -66,21 +84,21 @@ function never(_timestamp) {
|
||||
|
||||
// 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, },
|
||||
{ seconds: 1 * 60, name: '1m', tvRes: '1', filename: dailyFile( '1m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 3 * 60, name: '3m', tvRes: '3', filename: dailyFile( '3m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 5 * 60, name: '5m', tvRes: '5', filename: dailyFile( '5m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 10 * 60, name: '10m', tvRes: '10', filename: dailyFile('10m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 15 * 60, name: '15m', tvRes: '15', filename: dailyFile('15m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 30 * 60, name: '30m', tvRes: '30', filename: dailyFile('30m'), add: addDay, nextStart: nextDay, },
|
||||
{ seconds: 60 * 60, name: '1H', tvRes: '60', filename: monthlyFile( '1H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 120 * 60, name: '2H', tvRes: '120', filename: monthlyFile( '2H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 240 * 60, name: '4H', tvRes: '240', filename: monthlyFile( '4H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 480 * 60, name: '8H', tvRes: '480', filename: monthlyFile( '8H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 720 * 60, name: '12H', tvRes: '720', filename: monthlyFile('12H'), add: addMonth, nextStart: nextMonth, },
|
||||
{ seconds: 1440 * 60, name: '1D', tvRes: '1D', filename: yearlyFile( '1D'), add: addYear, nextStart: nextYear, },
|
||||
{ seconds: 2880 * 60, name: '2D', tvRes: '2D', filename: yearlyFile( '2D'), add: addYear, nextStart: nextYear, },
|
||||
{ seconds: 4320 * 60, name: '3D', tvRes: '3D', filename: yearlyFile( '3D'), add: addYear, nextStart: nextYear, },
|
||||
{ seconds: 10080 * 60, name: '1W', tvRes: '1W', filename: singleFile( '1W'), add: (x)=>x, nextStart: never, },
|
||||
]
|
||||
|
||||
const tvResMap = {}
|
||||
@@ -93,14 +111,21 @@ for (const res of resolutions)
|
||||
|
||||
|
||||
const seriesStarts = {}
|
||||
const ohlcCache = new LRUCache({max:20,ttl:3600*1000,})
|
||||
|
||||
|
||||
async function getUrl(url) {
|
||||
let result = ohlcCache[url]
|
||||
if (result)
|
||||
return result
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
// console.log('got response', response)
|
||||
if (response.ok)
|
||||
return await response.text()
|
||||
if (response.ok) {
|
||||
result = await response.text()
|
||||
ohlcCache[url] = result
|
||||
return result
|
||||
}
|
||||
else
|
||||
console.error(`could not fetch ${url}: status ${response.statusText}`)
|
||||
}
|
||||
@@ -112,7 +137,7 @@ async function getUrl(url) {
|
||||
|
||||
|
||||
export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
// console.log('loadOHLC', tvRes, new Date(1000*from), new Date(1000*to), symbol, contract);
|
||||
console.log('loadOHLC', tvRes, new Date(1000*from), new Date(1000*to), symbol, contract);
|
||||
let chainId
|
||||
let bars = [];
|
||||
let inverted = false;
|
||||
@@ -123,8 +148,10 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
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 ) {
|
||||
const start = ohlcStart(latestTime+period, period)
|
||||
// if (start<end)
|
||||
// console.log('filling', latestTime, price, new Date(start*1000), new Date(end*1000))
|
||||
for (let now=start; now < end; now += period ) {
|
||||
bars.push({time:now * 1000, open:price, high:price, low:price, close:price})
|
||||
latest = [now, price]
|
||||
}
|
||||
@@ -144,14 +171,14 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
|
||||
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)}`)
|
||||
const [start,end,price] = response.split(',')
|
||||
seriesStarts[baseUrl] = parseInt(start)
|
||||
console.log(`Series ${baseUrl} starts at ${new Date(start*1000)}`)
|
||||
}
|
||||
else {
|
||||
console.error(`Bad response while fetching ${baseUrl+'quote.csv'}`)
|
||||
@@ -161,85 +188,93 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
let start = from
|
||||
if (baseUrl in seriesStarts)
|
||||
start = Math.max(start, seriesStarts[baseUrl])
|
||||
|
||||
for(let now = start; now < to; now = res.nextStart(now)) {
|
||||
const end = res.nextStart(to)
|
||||
for(let now = start; now < end; 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}"`)
|
||||
let finished = false
|
||||
for( let ri=0; !finished && ri<responses.length; ri++ ) {
|
||||
let lineNum = 0
|
||||
const rows = responses[ri].split('\n')
|
||||
for( let rj=0; rj<rows.length; rj++) {
|
||||
const line = rows[rj];
|
||||
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])
|
||||
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 < from || time >= to)
|
||||
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}`)
|
||||
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 < from || 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 (time!==null) {
|
||||
if (time >= to) {
|
||||
console.log('time is past end of request:', time)
|
||||
finished = true
|
||||
break
|
||||
}
|
||||
if (close!==null) {
|
||||
if (time >= from) {
|
||||
fill(time, res.seconds)
|
||||
const bar = {time:time*1000, open, high, low, close};
|
||||
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') }
|
||||
console.log(`processed ${lineNum} lines`)
|
||||
}
|
||||
// console.log('loadOHLC prefill bars', bars)
|
||||
fill(to, res.seconds)
|
||||
// console.log('loadOHLC bars', bars)
|
||||
return bars
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user