tv DataFeed rework
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
|
||||
|
||||
const OHLC_START = new Date(1231027200*1000) // Sunday January 4th, 2009 just before Bitcoin Genesis
|
||||
// Sunday January 4th, 2009 just before Bitcoin Genesis
|
||||
const OHLC_START = 1231027200
|
||||
|
||||
|
||||
export function nearestOhlcStart(time, periodSeconds=null) {
|
||||
export function ohlcStart(timestamp, periodSeconds=null) {
|
||||
if (periodSeconds===null)
|
||||
periodSeconds = useChartOrderStore().intervalSecs
|
||||
return Math.round((time-OHLC_START) / periodSeconds) * periodSeconds + OHLC_START
|
||||
return Math.floor((timestamp-OHLC_START) / periodSeconds) * periodSeconds + OHLC_START
|
||||
}
|
||||
|
||||
export function pointsToOhlcStart(points) {
|
||||
export function nearestOhlcStart(timestamp, periodSeconds=null) {
|
||||
if (periodSeconds===null)
|
||||
periodSeconds = useChartOrderStore().intervalSecs
|
||||
return Math.round((timestamp-OHLC_START) / periodSeconds) * periodSeconds + OHLC_START
|
||||
}
|
||||
|
||||
export function pointsToTvOhlcStart(points) {
|
||||
return points === null ? null : points.map((p) => {
|
||||
return {time: nearestOhlcStart(p.time), price: p.price}
|
||||
return {time: nearestOhlcStart(p.time/1000)*1000, price: p.price}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ function changeSymbol(symbol) {
|
||||
|
||||
function changeInterval(interval, _timeframe) {
|
||||
co.intervalSecs = intervalToSeconds(interval)
|
||||
DataFeed.intervalChanged(co.intervalSecs)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
// import {subscribeOnStream, unsubscribeFromStream,} from './streaming.js';
|
||||
|
||||
import {loadOHLC} from './ohlc.js';
|
||||
import {convertTvResolution, loadOHLC} from './ohlc.js';
|
||||
import {metadata} from "@/version.js";
|
||||
import FlexSearch from "flexsearch";
|
||||
import {useChartOrderStore} from "@/orderbuild.js";
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {subOHLC, unsubOHLC} from "@/blockchain/ohlcs.js";
|
||||
import {socket} from "@/socket.js";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
import {timestamp} from "@/misc.js";
|
||||
|
||||
// disable debug messages logging
|
||||
let console = { log: function() {} }
|
||||
const DEBUG_LOGGING = false
|
||||
const log = DEBUG_LOGGING ? console.log : ()=>{}
|
||||
|
||||
// this file manages connecting data to TradingView using their DataFeed API.
|
||||
// https://www.tradingview.com/charting-library-docs/latest/connecting_data/Datafeed-API/#integrate-datafeed-api
|
||||
// see ohlc.js for fetching dexorder ohlc files
|
||||
|
||||
|
||||
// in order of priority
|
||||
const quoteSymbols = [
|
||||
'USDT',
|
||||
'USDC',
|
||||
'TUSD',
|
||||
'GUSD',
|
||||
'BUSD',
|
||||
'MUSD',
|
||||
'DAI',
|
||||
'CRVUSD',
|
||||
'EURC',
|
||||
'EURS',
|
||||
'EURI',
|
||||
'WBTC',
|
||||
'WETH',
|
||||
]
|
||||
|
||||
let feeDropdown = null
|
||||
let widget = null
|
||||
@@ -27,7 +48,7 @@ export function initFeeDropdown(w) {
|
||||
{
|
||||
title: 'Fees',
|
||||
tooltip: 'Choose Fee Tier',
|
||||
items: [/*{title: 'Automatic Fee Selection', onSelect: () => {console.log('autofees')}}*/],
|
||||
items: [/*{title: 'Automatic Fee Selection', onSelect: () => {log('autofees')}}*/],
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28"><g fill="none" stroke="currentColor"><circle cx="10" cy="10" r="2.5"/><circle cx="18" cy="18" r="2.5"/><path stroke-linecap="square" d="M17.5 7.5l-7 13"/></g></svg>`,
|
||||
}
|
||||
).then(dropdown => {feeDropdown = dropdown; updateFeeDropdown()})
|
||||
@@ -54,7 +75,7 @@ function updateFeeDropdown() {
|
||||
|
||||
function selectPool(p) {
|
||||
const co = useChartOrderStore();
|
||||
if ( co.selectedPool === null || co.selectedPool[0] !== p[0] || co.selectedPool[1] !== p[0]) {
|
||||
if ( co.selectedPool === null || co.selectedPool[0] !== p[0] || co.selectedPool[1] !== p[1]) {
|
||||
co.selectedPool = p
|
||||
}
|
||||
}
|
||||
@@ -91,7 +112,7 @@ const configurationData = {
|
||||
|
||||
const tokenMap = {}
|
||||
const poolMap = {}
|
||||
let _symbols = null
|
||||
let _symbols = null // keyed by the concatenated hex addrs of the token pair e.g. '0xf3Ed85D882b5d9A67fC10dBf8f9AA991212983aA' + '0x6cdC5106DC100115E6C310539Fe44a61b3EEa6C4'
|
||||
|
||||
const indexer = new FlexSearch.Document({
|
||||
document: {id: 'id', index: ['fn', 'as[]', 'b', 'q', 'bs', 'qs', 'e', 'd']}, // this must match what is generated for the index object in addSymbol()
|
||||
@@ -102,18 +123,19 @@ const indexer = new FlexSearch.Document({
|
||||
const indexes = {}
|
||||
const symbolsSeen = {} // keyed by (base,quote) so we only list one pool per pair even if there are many fee tiers
|
||||
|
||||
// todo add chainIds to all these keys
|
||||
function addSymbol(p, base, quote, inverted) {
|
||||
const symbol = base.s + '/' + quote.s
|
||||
const symbol = base.s + quote.s
|
||||
const exchange = ['UNIv2', 'UNIv3'][p.e]
|
||||
const full_name = exchange + ':' + symbol // + '%' + formatFee(fee)
|
||||
let key = `${base.a}${quote.a}`
|
||||
console.log('addSymbol', p, base, quote, inverted, key)
|
||||
const key = `${base.a}/${quote.a}`
|
||||
log('addSymbol', p, base, quote, inverted, key)
|
||||
if (symbolsSeen[key]) {
|
||||
// add this pool's address to the existing symbol as an additional fee tier
|
||||
const symbolInfo = _symbols[key];
|
||||
symbolInfo.pools.push([p.a, p.f])
|
||||
symbolInfo.pools.sort((a,b)=>a[1]-b[1])
|
||||
console.log('integrated symbol', _symbols[key])
|
||||
log('integrated symbol', symbolInfo)
|
||||
indexes[key].as.push(p.a)
|
||||
return
|
||||
}
|
||||
@@ -129,7 +151,7 @@ function addSymbol(p, base, quote, inverted) {
|
||||
}
|
||||
if (defaultSymbol===null)
|
||||
defaultSymbol = _symbols[key]
|
||||
console.log('new symbol', key, _symbols[key])
|
||||
log('new symbol', key, _symbols[key])
|
||||
indexes[key] = {
|
||||
// key
|
||||
id: key,
|
||||
@@ -156,12 +178,12 @@ function addSymbol(p, base, quote, inverted) {
|
||||
// return str
|
||||
// }
|
||||
|
||||
async function getAllSymbols() {
|
||||
function getAllSymbols() {
|
||||
if (_symbols===null) {
|
||||
const chainId = useStore().chainId;
|
||||
const md = metadata[chainId]
|
||||
if(!md) {
|
||||
console.log('could not get metadata for chain', chainId)
|
||||
log('could not get metadata for chain', chainId)
|
||||
return []
|
||||
}
|
||||
_symbols = {}
|
||||
@@ -172,47 +194,35 @@ async function getAllSymbols() {
|
||||
const base = tokenMap[p.b];
|
||||
const quote = tokenMap[p.q];
|
||||
if (!base) {
|
||||
console.log(`No token ${p.b} found`)
|
||||
log(`No token ${p.b} found`)
|
||||
return
|
||||
}
|
||||
if (!quote) {
|
||||
console.log(`No token ${p.q} found`)
|
||||
log(`No token ${p.q} found`)
|
||||
return
|
||||
}
|
||||
addSymbol(p, base, quote, false);
|
||||
addSymbol(p, quote, base, true);
|
||||
// todo check quotes symbol list for inversion hint
|
||||
let basePriority = quoteSymbols.indexOf(base.s)
|
||||
let quotePriority = quoteSymbols.indexOf(quote.s)
|
||||
if (basePriority === -1)
|
||||
basePriority = Number.MAX_SAFE_INTEGER
|
||||
if (quotePriority === -1)
|
||||
quotePriority = Number.MAX_SAFE_INTEGER
|
||||
const showInverted = basePriority < quotePriority
|
||||
if (showInverted)
|
||||
addSymbol(p, quote, base, true);
|
||||
else
|
||||
addSymbol(p, base, quote, false);
|
||||
})
|
||||
console.log('indexes', indexes)
|
||||
log('indexes', indexes)
|
||||
Object.values(indexes).forEach(indexer.add.bind(indexer))
|
||||
}
|
||||
console.log('symbols', _symbols)
|
||||
log('symbols', _symbols)
|
||||
return _symbols
|
||||
}
|
||||
|
||||
export function lookupSymbol(key) { // lookup by fullname
|
||||
return _symbols[key]
|
||||
}
|
||||
|
||||
export function lookupBaseQuote(baseAddr, quoteAddr) {
|
||||
return _symbols[`${baseAddr}${quoteAddr}`]
|
||||
}
|
||||
|
||||
function poolIsInverted() {
|
||||
return useChartOrderStore().selectedSymbol.inverted
|
||||
}
|
||||
|
||||
export function maybeInvertBar (bar) {
|
||||
if (poolIsInverted()) {
|
||||
bar.open = 1/bar.open
|
||||
let high = bar.high
|
||||
bar.high = 1/bar.low
|
||||
bar.low = 1/high
|
||||
bar.close = 1/bar.close
|
||||
console.log("bar inverted")
|
||||
} else {
|
||||
console.log("bar NOT inverted")
|
||||
}
|
||||
return bar
|
||||
return getAllSymbols()[key]
|
||||
}
|
||||
|
||||
function checkBar(bar, msg) {
|
||||
@@ -226,39 +236,81 @@ function checkBar(bar, msg) {
|
||||
let h_l = bar.high - bar.low
|
||||
|
||||
if (o_l<0||c_l<0||h_o<0||h_c<0||h_l<0) {
|
||||
console.log(msg, "bar.high/low inconsistent:", bar)
|
||||
if (o_l<0) console.log("bar inconsistent: open-low: ", o_l)
|
||||
if (c_l<0) console.log("bar inconsistent: close-low: ", c_l)
|
||||
if (h_o<0) console.log("bar inconsistent: high-open: ", h_o)
|
||||
if (h_c<0) console.log("bar inconsistent: high-close:", h_c)
|
||||
if (h_l<0) console.log("bar inconsistent: high-low: ", h_l)
|
||||
log(msg, "bar.high/low inconsistent:", bar)
|
||||
if (o_l<0) log("bar inconsistent: open-low: ", o_l)
|
||||
if (c_l<0) log("bar inconsistent: close-low: ", c_l)
|
||||
if (h_o<0) log("bar inconsistent: high-open: ", h_o)
|
||||
if (h_c<0) log("bar inconsistent: high-close:", h_c)
|
||||
if (h_l<0) log("bar inconsistent: high-low: ", h_l)
|
||||
} else {
|
||||
console.log(msg, "bar diffs:", bar)
|
||||
console.log("bar diff: open-low: ", o_l)
|
||||
console.log("bar diff: close-low: ", c_l)
|
||||
console.log("bar diff: high-open: ", h_o)
|
||||
console.log("bar diff: high-close:", h_c)
|
||||
console.log("bar diff: high-low: ", h_l)
|
||||
log(msg, "bar diffs:", bar)
|
||||
log("bar diff: open-low: ", o_l)
|
||||
log("bar diff: close-low: ", c_l)
|
||||
log("bar diff: high-open: ", h_o)
|
||||
log("bar diff: high-close:", h_c)
|
||||
log("bar diff: high-low: ", h_l)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function invertOhlcs(bars) {
|
||||
const result = []
|
||||
for (const bar of bars) {
|
||||
const h = bar.high
|
||||
result.push({
|
||||
time: bar.time,
|
||||
open: 1/bar.open,
|
||||
high: 1/bar.low,
|
||||
low: 1/h,
|
||||
close: 1/bar.close,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
const subByTvSubId = {}
|
||||
const subByKey = {}
|
||||
|
||||
class RealtimeSubscription {
|
||||
|
||||
constructor(chainId, poolAddr, res, symbol, tvSubId, onRealtimeCb, onResetCacheCb ) {
|
||||
this.chainId = chainId
|
||||
this.poolAddr = poolAddr
|
||||
this.res = res
|
||||
this.symbol = symbol
|
||||
this.tvSubId = tvSubId
|
||||
this.onRealtimeCb = onRealtimeCb
|
||||
this.onResetCacheCb = onResetCacheCb
|
||||
this.key = `${chainId}|${poolAddr}|${res.name}`
|
||||
subByTvSubId[this.tvSubId] = this
|
||||
subByKey[this.key] = this
|
||||
}
|
||||
|
||||
close() {
|
||||
delete subByTvSubId[this.tvSubId]
|
||||
delete subByKey[this.key]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const DataFeed = {
|
||||
onReady: (callback) => {
|
||||
console.log('[onReady]: Method call');
|
||||
onReady(callback) {
|
||||
log('[onReady]: Method call');
|
||||
setTimeout(() => callback(configurationData));
|
||||
},
|
||||
|
||||
searchSymbols: async (
|
||||
async searchSymbols(
|
||||
userInput,
|
||||
exchange,
|
||||
symbolType,
|
||||
onResultReadyCallback,
|
||||
) => {
|
||||
console.log('[searchSymbols]: Method call');
|
||||
) {
|
||||
log('[searchSymbols]: Method call');
|
||||
// todo limit results by exchange. use a separate indexer per exchange?
|
||||
const found = indexer.search(userInput, 10)
|
||||
console.log('found', found)
|
||||
log('found', found)
|
||||
const result = []
|
||||
for (const f of found)
|
||||
for (const key of f.result)
|
||||
@@ -266,18 +318,29 @@ export const DataFeed = {
|
||||
onResultReadyCallback(result);
|
||||
},
|
||||
|
||||
resolveSymbol: async (
|
||||
async resolveSymbol(
|
||||
symbolName,
|
||||
onSymbolResolvedCallback,
|
||||
onResolveErrorCallback,
|
||||
extension
|
||||
) => {
|
||||
console.log('[resolveSymbol]: Method call', symbolName);
|
||||
const symbols = await getAllSymbols();
|
||||
) {
|
||||
setTimeout(async ()=>
|
||||
await this.doResolveSymbol(symbolName,onSymbolResolvedCallback,onResolveErrorCallback,extension),
|
||||
0)
|
||||
},
|
||||
|
||||
async doResolveSymbol(
|
||||
symbolName,
|
||||
onSymbolResolvedCallback,
|
||||
onResolveErrorCallback,
|
||||
extension
|
||||
) {
|
||||
log('[resolveSymbol]: Method call', symbolName);
|
||||
const symbols = getAllSymbols();
|
||||
const symbolItem = symbolName === 'default' ? defaultSymbol : symbols[symbolName]
|
||||
console.log('symbol resolved?', symbolItem)
|
||||
log('symbol resolved?', symbolItem)
|
||||
if (!symbolItem) {
|
||||
console.log('[resolveSymbol]: Cannot resolve symbol', symbolName);
|
||||
log('[resolveSymbol]: Cannot resolve symbol', symbolName);
|
||||
onResolveErrorCallback('cannot resolve symbol');
|
||||
return;
|
||||
}
|
||||
@@ -310,143 +373,298 @@ export const DataFeed = {
|
||||
// volume_precision: 2,
|
||||
data_status: 'streaming',
|
||||
};
|
||||
console.log('[resolveSymbol]: Symbol resolved', symbolName);
|
||||
onSymbolResolvedCallback(symbolInfo);
|
||||
log('[resolveSymbol]: Symbol resolved', symbolName);
|
||||
onSymbolResolvedCallback(symbolInfo)
|
||||
},
|
||||
|
||||
getBars: async (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
|
||||
async getBars(symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) {
|
||||
const { from, to, firstDataRequest } = periodParams;
|
||||
console.log('[getBars]: Method call', symbolInfo, resolution, from, to);
|
||||
log('[getBars]: Method call', symbolInfo, resolution, from, to);
|
||||
try {
|
||||
// todo need to consider the selected fee tier
|
||||
let bars, metadata;
|
||||
const pool = useChartOrderStore().selectedPool;
|
||||
[bars, metadata] = await loadOHLC(lookupSymbol(symbolInfo.ticker), pool[0], from, to, resolution); // This is the one that does all the work
|
||||
if (firstDataRequest) {
|
||||
lastBarsCache.set(symbolInfo.full_name, {
|
||||
...bars[bars.length - 1],
|
||||
});
|
||||
}
|
||||
console.log(`[getBars]: returned ${bars.length} bar(s), and metadata ${metadata}`);
|
||||
console.log('[getBars]: bars=', bars);
|
||||
onHistoryCallback(bars, metadata);
|
||||
const [poolAddr, poolFee] = useChartOrderStore().selectedPool;
|
||||
await getAllSymbols()
|
||||
const symbol = lookupSymbol(symbolInfo.ticker);
|
||||
const res = convertTvResolution(resolution)
|
||||
const key = `${useStore().chainId}|${poolAddr}|${res.name}`
|
||||
let bars = await loadOHLC(symbol, poolAddr, from, to, resolution); // This is the one that does all the work
|
||||
this.updatePushedCache(key, bars)
|
||||
if (symbol.inverted)
|
||||
bars = invertOhlcs(bars)
|
||||
log(`[getBars]: returned ${bars.length} bar(s), and metadata ${metadata}`);
|
||||
log('[getBars]: bars=', bars);
|
||||
// noData should be set only if no bars are in the requested period and earlier.
|
||||
// In our case, we are guaranteed to have contiguous samples.
|
||||
// So we only return no bars (bars.length==0) if:
|
||||
// 1. period is entirely before first data available.
|
||||
// 2. period is entirely after last data available.
|
||||
// Returning noData based on bars.length works perfectly assuming that TV never asks for case 2.
|
||||
// This is probably not a safe assumption. The alternative would be to search
|
||||
// backward to find beginning of history. How far to search?
|
||||
const noData = bars.length === 0;
|
||||
onHistoryCallback(bars, {noData});
|
||||
} catch (error) {
|
||||
console.log('[getBars]: Get error', error);
|
||||
log('[getBars]: Get error', error);
|
||||
onErrorCallback(error);
|
||||
}
|
||||
},
|
||||
|
||||
subscribeBarsOnRealtimeCallback: null,
|
||||
|
||||
subscribeBars: (
|
||||
subscribeBars(
|
||||
symbolInfo,
|
||||
resolution,
|
||||
onRealtimeCallback,
|
||||
onRealtimeCallback2,
|
||||
subscriberUID,
|
||||
onResetCacheNeededCallback,
|
||||
) => {
|
||||
console.log('[subscribeBars]', symbolInfo, resolution, subscriberUID);
|
||||
const chainId = useStore().chainId;
|
||||
const poolAddr = useChartOrderStore().selectedPool[0];
|
||||
const period = tvResolutionToPeriodString(resolution);
|
||||
subscriptions[subscriberUID] = [chainId, poolAddr, period, onRealtimeCallback, onResetCacheNeededCallback]
|
||||
const key = `${chainId}|${poolAddr}|${period}`
|
||||
if (key in subscriptionCallbacks)
|
||||
subscriptionCallbacks[key].push(subscriberUID)
|
||||
else
|
||||
subscriptionCallbacks[key] = [subscriberUID]
|
||||
console.log('sub', key)
|
||||
subOHLC(chainId, poolAddr, period)
|
||||
},
|
||||
) {
|
||||
|
||||
unsubscribeBars: (subscriberUID) => {
|
||||
console.log('[unsubscribeBars]', subscriberUID);
|
||||
const [chainId, poolAddr, period] = subscriptions[subscriberUID]
|
||||
unsubOHLC(chainId, poolAddr, period)
|
||||
delete subscriptions[subscriberUID]
|
||||
const key = `${chainId}|${poolAddr}|${period}`;
|
||||
console.log('unsub',key)
|
||||
const remainingSubs = subscriptionCallbacks[key].filter((v)=>v!==subscriberUID)
|
||||
if (remainingSubs.length===0)
|
||||
delete subscriptionCallbacks[key]
|
||||
else
|
||||
subscriptionCallbacks[key] = remainingSubs
|
||||
},
|
||||
|
||||
poolCallbackState : {lastBar:{'chain|pool|period':null}},
|
||||
|
||||
poolCallback(chainId, poolPeriod, ohlcs) {
|
||||
console.log("poolCallback: chainId, pool, ohlcs:", chainId, poolPeriod, ohlcs)
|
||||
if (ohlcs == null) {
|
||||
console.log("poolCallback: ohlcs == null, nothing to do.")
|
||||
return;
|
||||
const oldCb = onRealtimeCallback2
|
||||
function onRealtimeCallback() {
|
||||
log('push bar', ...arguments)
|
||||
oldCb(...arguments)
|
||||
}
|
||||
const key = `${chainId}|${poolPeriod}`;
|
||||
const subscriptionUIDs = subscriptionCallbacks[key];
|
||||
if (subscriptionUIDs===undefined) {
|
||||
console.log('unsubbing abandoned subscription', poolPeriod)
|
||||
socket.emit('unsubOHLCs', chainId, [poolPeriod])
|
||||
|
||||
log('[subscribeBars]', symbolInfo, resolution, subscriberUID);
|
||||
const symbol = getAllSymbols()[symbolInfo.full_name]
|
||||
const co = useChartOrderStore()
|
||||
|
||||
|
||||
// todo redo symbolInfo: the full_name passed to TV should just be the pool addr. when searching a symbol, go ahead and select the first/most liquid automatically
|
||||
let poolAddr = null
|
||||
const selectedFee = co.selectedPool[1];
|
||||
for (const [addr, fee] of symbol.pools) {
|
||||
if (fee === selectedFee) {
|
||||
poolAddr = addr
|
||||
break
|
||||
}
|
||||
}
|
||||
if (poolAddr===null) {
|
||||
console.error(`Could not find pool for fee ${selectedFee}: ${symbol.pools}`)
|
||||
return
|
||||
}
|
||||
function onRealtimeCallback() {
|
||||
for (const subId of subscriptionUIDs) {
|
||||
const [_chainId, _poolAddr, _period, _onRealtimeCallback, _onResetCacheNeededCallback] = subscriptions[subId]
|
||||
_onRealtimeCallback(...arguments)
|
||||
|
||||
|
||||
const chainId = useStore().chainId;
|
||||
|
||||
const res = convertTvResolution(resolution)
|
||||
const period = res.name
|
||||
console.log('subscription symbol', symbol, getAllSymbols())
|
||||
const sub = new RealtimeSubscription(chainId, poolAddr, res, symbol, subscriberUID, onRealtimeCallback, onResetCacheNeededCallback)
|
||||
log('sub', sub.key)
|
||||
subOHLC(chainId, poolAddr, period)
|
||||
this.startOHLCBumper()
|
||||
},
|
||||
|
||||
unsubscribeBars(subscriberUID) {
|
||||
log('[unsubscribeBars]', subscriberUID);
|
||||
const sub = subByTvSubId[subscriberUID]
|
||||
if (sub===undefined) {
|
||||
console.log(`warning: no subscription found for tvSubId ${subscriberUID}`)
|
||||
return
|
||||
}
|
||||
unsubOHLC(sub.chainId, sub.poolAddr, sub.res.name)
|
||||
log('unsub',sub.key)
|
||||
sub.close()
|
||||
delete this.pushedBars[sub.key]
|
||||
delete this.recentBars[sub.key]
|
||||
},
|
||||
|
||||
|
||||
// key-value format:
|
||||
// 'chain|pool|period': [[javatime, open, high, low, close], ...]
|
||||
pushedBars: {}, // bars actually sent to TradingView
|
||||
recentBars:{}, // bars received from server notifications
|
||||
|
||||
|
||||
pushRecentBars(key, recent) {
|
||||
this.recentBars[key] = recent
|
||||
const historical = this.pushedBars[key]
|
||||
if (historical!==undefined)
|
||||
this.overlapAndPush(key, recent)
|
||||
},
|
||||
|
||||
|
||||
overlapAndPush(key, ohlcs) {
|
||||
log('overlapAndPush',key,ohlcs)
|
||||
if (!ohlcs || ohlcs.length === 0) return
|
||||
|
||||
const sub = subByKey[key]
|
||||
// do not check reorgs on mock symbols because the dev environment price will be different from the main chain
|
||||
// price and reorgs would happen every time.
|
||||
const checkReorg = sub.symbol.x?.data === undefined
|
||||
const res = sub.res
|
||||
const period = res.seconds * 1000
|
||||
const bars = [] // to push
|
||||
const pushed = this.pushedBars[key]
|
||||
log('got pushed bars', pushed)
|
||||
let pi = 0 // pushed index
|
||||
let time
|
||||
let price
|
||||
if (pushed === undefined) {
|
||||
time = ohlcs[0].time
|
||||
price = ohlcs[0].close
|
||||
}
|
||||
else {
|
||||
const last = pushed.length - 1
|
||||
time = pushed[last].time
|
||||
price = pushed[last].close
|
||||
}
|
||||
log('last time/price', time, price)
|
||||
for( const ohlc of ohlcs ) {
|
||||
log('handle ohlc', ohlc)
|
||||
// forward the pi index to at least the current ohlc time
|
||||
while( pi < pushed.length && pushed[pi].time < ohlc.time ) pi++
|
||||
|
||||
if (pi < pushed.length-1) {
|
||||
// finalized bars must match the previous push exactly or else there was a reorg and we need to reset
|
||||
const p = pushed[pi]
|
||||
log('check reorg', checkReorg, pi, p, ohlc)
|
||||
if (checkReorg &&
|
||||
(p.time !== ohlc.time || p.open !== ohlc.open || p.high !== ohlc.high ||
|
||||
p.low !== ohlc.low || p.close !== ohlc.close) )
|
||||
{
|
||||
console.log('RESET TV CACHE')
|
||||
return this.resetCache(key)
|
||||
}
|
||||
}
|
||||
else {
|
||||
// the last pushed bar and anything after it can be sent to TV
|
||||
while (time + period < ohlc.time) {
|
||||
// fill gap
|
||||
time += period
|
||||
const bar = {time, open: price, high: price, low: price, close: price};
|
||||
log('fill', bar)
|
||||
bars.push(bar)
|
||||
}
|
||||
bars.push(ohlc)
|
||||
time = ohlc.time
|
||||
price = ohlc.close
|
||||
}
|
||||
}
|
||||
return this.pushToTV(key, bars)
|
||||
},
|
||||
|
||||
function onResetCacheNeededCallback() {
|
||||
for (const subId of subscriptionUIDs) {
|
||||
const [_chainId, _poolAddr, _period, _onRealtimeCallback, _onResetCacheNeededCallback] = subscriptions[subId]
|
||||
_onResetCacheNeededCallback(...arguments)
|
||||
}
|
||||
|
||||
resetCache(key) {
|
||||
log('resetting TV data cache')
|
||||
const sub = subByKey[key]
|
||||
if (sub===undefined) {
|
||||
console.log(`warning: no subscription found for dexorder key ${key}`)
|
||||
return
|
||||
}
|
||||
sub.onResetCacheCb()
|
||||
},
|
||||
|
||||
let ohlc = ohlcs.at(-1);
|
||||
console.log("poolCallBack ohlc:", new Date(Number(ohlc[0])*1000).toGMTString(), ohlc)
|
||||
for (let i = 0; i<ohlc.length; i++) if (ohlc[i]!=null) ohlc[i] = Number(ohlc[i])
|
||||
// for (const ohlc of ohlcs) {
|
||||
let date = new Date(ohlc[0]*1000)
|
||||
let close = ohlc[4] // close
|
||||
let bar = {
|
||||
time: date.getTime(),
|
||||
open: ohlc[1] ?? close, // open
|
||||
high: ohlc[2] ?? close, // high
|
||||
low: ohlc[3] ?? close, // low
|
||||
|
||||
updatePushedCache(key, ohlcs) {
|
||||
log('updatePushedCache', key, ohlcs)
|
||||
if (!(key in this.pushedBars))
|
||||
this.pushedBars[key] = ohlcs
|
||||
else {
|
||||
const prev = this.pushedBars[key]
|
||||
log('prev pushed', prev)
|
||||
const endTime = prev[prev.length - 1].time;
|
||||
if (endTime === ohlcs[0].time)
|
||||
// the new ohlc's overlap the old time, so exclude the most recent historical item, which gets replaced
|
||||
this.pushedBars[key] = [...prev.slice(0, -1), ...ohlcs]
|
||||
else if (endTime <= ohlcs[ohlcs.length-1].time) {
|
||||
// no overlap of any bars. full append.
|
||||
this.pushedBars[key] = [...prev, ...ohlcs]
|
||||
}
|
||||
// otherwise the ohlc's being pushed are =>older<= than the ones in the pushedBars cache. This happens
|
||||
// because TV can request data pages (using getBars()) out of chronological order.
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
pushToTV(key, ohlcs) {
|
||||
if (ohlcs.length===0) return
|
||||
log('pushing bars to tv', ohlcs)
|
||||
this.updatePushedCache(key, ohlcs); // we cache the raw bars before inversion so they match dexorder data sources
|
||||
const sub = subByKey[key]
|
||||
if (sub===undefined) {
|
||||
console.log(`warning: could not find subscription for dexorder sub key ${key}`)
|
||||
return
|
||||
}
|
||||
if (sub.symbol.inverted)
|
||||
ohlcs = invertOhlcs(ohlcs)
|
||||
for (const ohlc of ohlcs)
|
||||
sub.onRealtimeCb(ohlc)
|
||||
},
|
||||
|
||||
|
||||
poolCallback(chainId, poolPeriod, ohlcs) {
|
||||
const key = `${chainId}|${poolPeriod}`;
|
||||
const bars = []
|
||||
for (const ohlc of ohlcs) {
|
||||
let close = parseFloat(ohlc[4]) // close
|
||||
const bar = {
|
||||
time: ohlc[0] * 1000,
|
||||
open: ohlc[1] ? parseFloat(ohlc[1]) : close, // open
|
||||
high: ohlc[2] ? parseFloat(ohlc[2]) : close, // high
|
||||
low: ohlc[3] ? parseFloat(ohlc[3]) : close, // low
|
||||
close: close,
|
||||
}
|
||||
checkBar(bar, "poolCallback, before inversion:")
|
||||
bar = maybeInvertBar(bar)
|
||||
checkBar(bar, "poolCallback, after inversion:")
|
||||
console.log('DataFeed.poolCallback', date.toGMTString(), ohlcs, bar)
|
||||
let lastBar = DataFeed.poolCallbackState.lastBar[key]
|
||||
// No last bar then initialize bar
|
||||
if (lastBar===undefined) {
|
||||
console.log('DataFeed.poolCallback', new Date(bar.time).toGMTString(), 'lastBar=', bar)
|
||||
onRealtimeCallback(bar)
|
||||
DataFeed.poolCallbackState.lastBar[key] = bar
|
||||
bars.push(bar)
|
||||
}
|
||||
return this.pushRecentBars(key, bars)
|
||||
},
|
||||
|
||||
intervalChanged(seconds) {
|
||||
// rollover bumper
|
||||
// this timer function creates a new bar when the period rolls over
|
||||
this.startOHLCBumper()
|
||||
},
|
||||
|
||||
// The OHLC bumper advances bars at the end of the period. If the price doesn't change, no new data will arrive, so the
|
||||
// new bar is implicit and must be generated dynamically.
|
||||
|
||||
startOHLCBumper() {
|
||||
const co = useChartOrderStore()
|
||||
if (_rolloverBumper !== null)
|
||||
clearTimeout(_rolloverBumper)
|
||||
const period = co.intervalSecs;
|
||||
if (period === 0)
|
||||
return
|
||||
const now = Date.now()
|
||||
const nextRollover = ohlcStart(now/1000 + period)*1000 + 2000 // two second delay to wait for server data
|
||||
const delay = nextRollover - now
|
||||
_rolloverBumper = setTimeout(this.bumpOHLC.bind(this), delay)
|
||||
},
|
||||
|
||||
bumpOHLC() {
|
||||
log('bumpOHLC')
|
||||
_rolloverBumper = null
|
||||
const secs = useChartOrderStore().intervalSecs;
|
||||
for (const sub of Object.values(subByKey)) {
|
||||
log('check bump', sub.res.seconds, secs)
|
||||
if (sub.res.seconds === secs) {
|
||||
const pushed = this.pushedBars[sub.key]
|
||||
log('check pushed', pushed)
|
||||
if (pushed !== undefined && pushed.length > 0) {
|
||||
const lastBar = pushed[pushed.length - 1]
|
||||
const price = lastBar.close
|
||||
const now = timestamp() * 1000
|
||||
const period = sub.res.seconds * 1000
|
||||
const fills = []
|
||||
for( let time=lastBar.time + period; time < now; time += period ) {
|
||||
log('pushing bump', time, price)
|
||||
const bar = {time, open: price, high: price, low: price, close: price}
|
||||
fills.push(bar)
|
||||
}
|
||||
this.pushToTV(sub.key, fills)
|
||||
}
|
||||
else {
|
||||
log('warning: bumpOHLC() found no previous bars')
|
||||
}
|
||||
}
|
||||
// bar time is less than last bar then ignore
|
||||
else if (bar.time < lastBar.time ) {
|
||||
}
|
||||
// bar time equal to last bar then replace last bar
|
||||
else if (bar.time === lastBar.time ) {
|
||||
console.log('DataFeed.poolCallback', new Date(bar.time).toGMTString(), 'lastBar=', bar)
|
||||
if (bar.high < lastBar.high) console.log("bar.high < lastBar.high (lastbar=)")
|
||||
if (bar.low > lastBar.low) console.log("bar.low > lastBar.low (lastbar=)")
|
||||
onRealtimeCallback(bar)
|
||||
DataFeed.poolCallbackState.lastBar[key] = bar
|
||||
}
|
||||
// new bar, then render last and replace last bar
|
||||
else {
|
||||
console.log('DataFeed.poolCallback', new Date(bar.time).toGMTString(), 'lastBar=', bar)
|
||||
onRealtimeCallback(bar)
|
||||
DataFeed.poolCallbackState.lastBar[key] = bar
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
this.startOHLCBumper()
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
let _rolloverBumper = null
|
||||
let defaultSymbol = null
|
||||
const subscriptions = {}
|
||||
const subscriptionCallbacks = {}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {useStore} from "@/store/store.js";
|
||||
import {nearestOhlcStart} from "@/charts/chart-misc.js";
|
||||
import {ohlcStart} from "@/charts/chart-misc.js";
|
||||
|
||||
|
||||
// support for Dexorder OHLC data files
|
||||
|
||||
|
||||
function dailyFile(resName) {
|
||||
@@ -61,27 +64,32 @@ function never(_timestamp) {
|
||||
}
|
||||
|
||||
|
||||
// noinspection PointlessArithmeticExpressionJS
|
||||
const resolutions = [
|
||||
{ period: 1, tvRes: '1', filename: dailyFile( '1m'), nextStart: nextDay, },
|
||||
{ period: 3, tvRes: '3', filename: dailyFile( '3m'), nextStart: nextDay, },
|
||||
{ period: 5, tvRes: '5', filename: dailyFile( '5m'), nextStart: nextDay, },
|
||||
{ period: 10, tvRes: '10', filename: dailyFile('10m'), nextStart: nextDay, },
|
||||
{ period: 15, tvRes: '15', filename: dailyFile('15m'), nextStart: nextDay, },
|
||||
{ period: 30, tvRes: '30', filename: dailyFile('30m'), nextStart: nextDay, },
|
||||
{ period: 60, tvRes: '60', filename: monthlyFile( '1H'), nextStart: nextMonth, },
|
||||
{ period: 120, tvRes: '120', filename: monthlyFile( '2H'), nextStart: nextMonth, },
|
||||
{ period: 240, tvRes: '240', filename: monthlyFile( '4H'), nextStart: nextMonth, },
|
||||
{ period: 480, tvRes: '480', filename: monthlyFile( '8H'), nextStart: nextMonth, },
|
||||
{ period: 720, tvRes: '720', filename: monthlyFile('12H'), nextStart: nextMonth, },
|
||||
{ period: 1440, tvRes: '1D', filename: yearlyFile( '1D'), nextStart: nextYear, },
|
||||
{ period: 2880, tvRes: '2D', filename: yearlyFile( '2D'), nextStart: nextYear, },
|
||||
{ period: 4320, tvRes: '3D', filename: yearlyFile( '3D'), nextStart: nextYear, },
|
||||
{ period: 10080, tvRes: '1W', filename: singleFile( '1W'), nextStart: never, },
|
||||
{ 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 resMap = {}
|
||||
const tvResMap = {}
|
||||
for (const res of resolutions)
|
||||
resMap[res.tvRes] = res
|
||||
tvResMap[res.tvRes] = res
|
||||
|
||||
const dxoResMap = {}
|
||||
for (const res of resolutions)
|
||||
dxoResMap[res.name] = res
|
||||
|
||||
|
||||
const seriesStarts = {}
|
||||
@@ -107,15 +115,19 @@ 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 = symbol.inverted;
|
||||
let inverted = false;
|
||||
let baseURL
|
||||
let latest = null // latest time, price
|
||||
|
||||
function fill(end, period) {
|
||||
if (latest===null) return
|
||||
const [start, price] = latest
|
||||
for (let now=nearestOhlcStart(start, period*60); now < end; now += period )
|
||||
const periodSecs = period * 60
|
||||
end = ohlcStart(end, periodSecs)
|
||||
for (let now=ohlcStart(start+periodSecs, periodSecs); now < end; now += periodSecs ) {
|
||||
bars.push({time:now * 1000, open:price, high:price, low:price, close:price})
|
||||
latest = [now, price]
|
||||
}
|
||||
}
|
||||
|
||||
if (symbol.x?.data) {
|
||||
@@ -130,7 +142,7 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
}
|
||||
baseURL += `${chainId}/${contract}/`
|
||||
|
||||
const res = resMap[tvRes]
|
||||
const res = tvResMap[tvRes]
|
||||
const fetches = []
|
||||
let start = from
|
||||
if (!(baseURL in seriesStarts)) {
|
||||
@@ -164,7 +176,6 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
let lineNum = 0
|
||||
response.split('\n').forEach((line) => {
|
||||
lineNum++
|
||||
// console.log(`processing line ${lineNum}`, line)
|
||||
const row = line.split(',')
|
||||
let time, open, high, low, close=null
|
||||
switch (row.length) {
|
||||
@@ -204,8 +215,9 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
close = parseFloat(row[4])
|
||||
if (inverted) {
|
||||
open = 1/open
|
||||
high = 1/high
|
||||
low = 1/low
|
||||
const h = high
|
||||
high = 1/low
|
||||
low = 1/h
|
||||
close = 1/close
|
||||
}
|
||||
break
|
||||
@@ -214,10 +226,8 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
break
|
||||
}
|
||||
if (close!==null) {
|
||||
// console.log(`filling up to ${time}`)
|
||||
fill(time, res.period)
|
||||
fill(time, res.seconds)
|
||||
const bar = {time:time*1000, open, high, low, close};
|
||||
// console.log('pushing bar', bar)
|
||||
bars.push(bar)
|
||||
latest = [time, close]
|
||||
}
|
||||
@@ -226,20 +236,21 @@ export async function loadOHLC (symbol, contract, from, to, tvRes) {
|
||||
}
|
||||
// else { console.log('response was empty') }
|
||||
}
|
||||
|
||||
fill(to, res.period)
|
||||
|
||||
// noData should be set only if no bars are in the requested period and earlier.
|
||||
// In our case, we are guaranteed to have contiguous samples.
|
||||
// So we only return no bars (bars.length==0) if:
|
||||
// 1. period is entirely before first data available.
|
||||
// 2. period is entirely after last data available.
|
||||
// Returning noData based on bars.length works perfectly assuming that TV never asks for case 2.
|
||||
// This is probably not a safe assumption. The alternative would be to search
|
||||
// backward to find beginning of history. How far to search?
|
||||
|
||||
let noData = bars.length === 0;
|
||||
// if (noData) console.log("noData == true!");
|
||||
// console.log('bars', bars)
|
||||
return [bars, {noData}];
|
||||
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]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape
|
||||
import {unique} from "@/misc.js";
|
||||
import {allocationText} from "@/orderbuild.js";
|
||||
import Color from "color";
|
||||
import {pointsToOhlcStart} from "@/charts/chart-misc.js";
|
||||
import {pointsToTvOhlcStart} from "@/charts/chart-misc.js";
|
||||
|
||||
|
||||
//
|
||||
@@ -202,7 +202,7 @@ export class Shape {
|
||||
// createShape(this.type, this.points, {overrides:this.props}, new ShapeTVCallbacks(this))
|
||||
options = {...options}
|
||||
options['overrides'] = props
|
||||
this.tvPoints = pointsToOhlcStart(points)
|
||||
this.tvPoints = pointsToTvOhlcStart(points)
|
||||
this.tvCallbacks = new ShapeTVCallbacks(this);
|
||||
const id = createShape(this.type, this.tvPoints, options, this.tvCallbacks)
|
||||
// todo set id?
|
||||
@@ -247,7 +247,7 @@ export class Shape {
|
||||
if (this.id === null)
|
||||
this.create()
|
||||
else {
|
||||
points = pointsToOhlcStart(points)
|
||||
points = pointsToTvOhlcStart(points)
|
||||
if (dirtyPoints(this.tvPoints, points)) {
|
||||
const s = this.tvShape();
|
||||
const lbe = s._model._lineBeingEdited
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
// import { parseFullSymbol } from './helpers.js';
|
||||
// import { subPrices } from '@/blockchain/prices.js';
|
||||
|
||||
// const socket = io('wss://streamer.cryptocompare.com');
|
||||
// const channelToSubscription = new Map();
|
||||
|
||||
// socket.on('connect', () => {
|
||||
// console.log('[socket] Connected');
|
||||
// });
|
||||
|
||||
// socket.on('disconnect', (reason) => {
|
||||
// console.log('[socket] Disconnected:', reason);
|
||||
// });
|
||||
|
||||
// socket.on('error', (error) => {
|
||||
// console.log('[socket] Error:', error);
|
||||
// });
|
||||
|
||||
// socket.on('m', data => {
|
||||
// console.log('[socket] Message:', data);
|
||||
// const [
|
||||
// eventTypeStr,
|
||||
// exchange,
|
||||
// fromSymbol,
|
||||
// toSymbol,
|
||||
// ,
|
||||
// ,
|
||||
// tradeTimeStr,
|
||||
// ,
|
||||
// tradePriceStr,
|
||||
// ] = data.split('~');
|
||||
|
||||
// if (parseInt(eventTypeStr) !== 0) {
|
||||
// // Skip all non-trading events
|
||||
// return;
|
||||
// }
|
||||
// const tradePrice = parseFloat(tradePriceStr);
|
||||
// const tradeTime = parseInt(tradeTimeStr);
|
||||
// const channelString = `0~${exchange}~${fromSymbol}~${toSymbol}`;
|
||||
// const subscriptionItem = channelToSubscription.get(channelString);
|
||||
// if (subscriptionItem === undefined) {
|
||||
// return;
|
||||
// }
|
||||
// const lastDailyBar = subscriptionItem.lastDailyBar;
|
||||
// const nextDailyBarTime = getNextDailyBarTime(lastDailyBar.time, subscriptionItem.resolution);
|
||||
|
||||
// console.log("tradeTime ", tradeTime, new Date(tradeTime))
|
||||
// console.log("lastDailyBar.time", lastDailyBar.time, new Date(lastDailyBar.time))
|
||||
// console.log("nextDailyBarTime ", nextDailyBarTime, new Date(nextDailyBarTime))
|
||||
|
||||
// let bar;
|
||||
// if (tradeTime >= nextDailyBarTime) {
|
||||
// bar = {
|
||||
// time: nextDailyBarTime,
|
||||
// open: tradePrice,
|
||||
// high: tradePrice,
|
||||
// low: tradePrice,
|
||||
// close: tradePrice,
|
||||
// };
|
||||
// console.log('[socket] Generate new bar', bar);
|
||||
// console.log("time:", bar.time.toString(), new Date(bar.time).toUTCString())
|
||||
// } else {
|
||||
// bar = {
|
||||
// ...lastDailyBar,
|
||||
// high: Math.max(lastDailyBar.high, tradePrice),
|
||||
// low: Math.min(lastDailyBar.low, tradePrice),
|
||||
// close: tradePrice,
|
||||
// };
|
||||
// console.log('[socket] Update the latest bar by price', tradePrice);
|
||||
// }
|
||||
// subscriptionItem.lastDailyBar = bar;
|
||||
|
||||
// // Send data to every subscriber of that symbol
|
||||
// subscriptionItem.handlers.forEach(handler => handler.callback(bar));
|
||||
// });
|
||||
|
||||
// function getNextDailyBarTime(barTime, res) {
|
||||
// const date = new Date(barTime);
|
||||
// const resDigits = res.slice(0, -1)
|
||||
// if (res.endsWith("W")) {
|
||||
// date.setDate(date.getDate() + parseInt(resDigits)*7);
|
||||
// } else if (res.endsWith("D")) {
|
||||
// date.setDate(date.getDate() + parseInt(resDigits));
|
||||
// } else {
|
||||
// date.setMinutes(date.getMinutes() + parseInt(res))
|
||||
// }
|
||||
// return date.getTime();
|
||||
// }
|
||||
|
||||
// export function subscribeOnStream(
|
||||
// symbolInfo,
|
||||
// resolution,
|
||||
// onRealtimeCallback,
|
||||
// subscriberUID,
|
||||
// onResetCacheNeededCallback,
|
||||
// lastDailyBar,
|
||||
// ) {
|
||||
// // return;
|
||||
// const parsedSymbol = parseFullSymbol(symbolInfo.full_name);
|
||||
// const channelString = `0~${parsedSymbol.exchange}~${parsedSymbol.fromSymbol}~${parsedSymbol.toSymbol}`;
|
||||
// const handler = {
|
||||
// id: subscriberUID,
|
||||
// callback: onRealtimeCallback,
|
||||
// };
|
||||
// let subscriptionItem = channelToSubscription.get(channelString);
|
||||
// if (subscriptionItem) {
|
||||
// // Already subscribed to the channel, use the existing subscription
|
||||
// subscriptionItem.handlers.push(handler);
|
||||
// return;
|
||||
// }
|
||||
// subscriptionItem = {
|
||||
// subscriberUID,
|
||||
// resolution,
|
||||
// lastDailyBar,
|
||||
// handlers: [handler],
|
||||
// };
|
||||
// channelToSubscription.set(channelString, subscriptionItem);
|
||||
// console.log('[subscribeBars]: Subscribe to streaming. Channel:', channelString);
|
||||
// socket.emit('SubAdd', { subs: [channelString] });
|
||||
// }
|
||||
|
||||
// export function unsubscribeFromStream(subscriberUID) {
|
||||
// // return;
|
||||
// // Find a subscription with id === subscriberUID
|
||||
// for (const channelString of channelToSubscription.keys()) {
|
||||
// const subscriptionItem = channelToSubscription.get(channelString);
|
||||
// const handlerIndex = subscriptionItem.handlers
|
||||
// .findIndex(handler => handler.id === subscriberUID);
|
||||
|
||||
// if (handlerIndex !== -1) {
|
||||
// // Remove from handlers
|
||||
// subscriptionItem.handlers.splice(handlerIndex, 1);
|
||||
|
||||
// if (subscriptionItem.handlers.length === 0) {
|
||||
// // Unsubscribe from the channel if it was the last handler
|
||||
// console.log('[unsubscribeBars]: Unsubscribe from streaming. Channel:', channelString);
|
||||
// socket.emit('SubRemove', { subs: [channelString] });
|
||||
// channelToSubscription.delete(channelString);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// function sim() {
|
||||
// // Assuming these variables hold the data you extracted earlier
|
||||
// const eventTypeStr = "0";
|
||||
// const exchange = "Uniswap";
|
||||
// const fromSymbol = "WETH";
|
||||
// const toSymbol = "USD";
|
||||
// const tradeTimeStr = (Date.now()).toString();
|
||||
// const tradePriceStr = (55+Date.now()%23).toString();
|
||||
|
||||
// // Constructing the original string
|
||||
// const data = [
|
||||
// eventTypeStr,
|
||||
// exchange,
|
||||
// fromSymbol,
|
||||
// toSymbol,
|
||||
// '', // Placeholder for the fifth element
|
||||
// '', // Placeholder for the sixth element
|
||||
// tradeTimeStr,
|
||||
// '', // Placeholder for the eighth element
|
||||
// tradePriceStr,
|
||||
// ].join('~');
|
||||
// socket._callbacks['$m'][0](data);
|
||||
// }
|
||||
|
||||
// window.sim = sim;
|
||||
// socket._callbacks['$connect'][0]();
|
||||
// setInterval(sim, 10*1000);
|
||||
;
|
||||
@@ -15,7 +15,7 @@ socket.on('disconnect', () => {
|
||||
})
|
||||
|
||||
socket.on('p', async (chainId, pool, price) => {
|
||||
// console.log('pool price from message', chainId, pool, price)
|
||||
console.log('pool price from message', chainId, pool, price)
|
||||
const s = useStore()
|
||||
if( s.chainId !== chainId )
|
||||
return
|
||||
@@ -27,7 +27,7 @@ socket.on('ohlc', async (chainId, poolPeriod, ohlcs) => {
|
||||
if (ohlcs && ohlcs.length) {
|
||||
const split = poolPeriod.indexOf('|')
|
||||
const pool = poolPeriod.slice(0,split)
|
||||
useStore().poolPrices[[chainId, pool]] = ohlcs[ohlcs.length - 1][4] // closing price
|
||||
useStore().poolPrices[[chainId, pool]] = parseFloat(ohlcs[ohlcs.length - 1][4]) // closing price
|
||||
}
|
||||
DataFeed.poolCallback(chainId, poolPeriod, ohlcs)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user