diff --git a/src/blockchain/ohlcs.js b/src/blockchain/ohlcs.js index 913ea25..77ef4f9 100644 --- a/src/blockchain/ohlcs.js +++ b/src/blockchain/ohlcs.js @@ -1,14 +1,48 @@ import {socket} from "@/socket.js"; -import {useStore} from "@/store/store.js"; -import {Exchange} from "@/blockchain/orderlib.js"; -import {uniswapV3PoolAddress} from "@/blockchain/uniswap.js"; -import {ethers, FixedNumber} from "ethers"; -import {uniswapV3PoolAbi} from "@/blockchain/abi.js"; const ohlcSubCounts = {} // key is route and value is a subscription counter export const WIDE_PRICE_FORMAT = {decimals:38, width:512, signed:false}; // 38 decimals is 127 bits +export function subOHLC( chainId, pool, period ) { + const key = `${pool}|${period}` + const ckey = `${chainId}|${key}` + if (!(key in ohlcSubCounts) || ohlcSubCounts[ckey] === 0) { + ohlcSubCounts[ckey] = 1 + socket.emit('subOHLCs', chainId, [key]) + } else + ohlcSubCounts[ckey]++ +} + + +export function unsubOHLC( chainId, pool, period ) { + for( const [pool,period] of poolPeriods ) { + const key = `${pool}|${period}` + const ckey = `${chainId}|${key}` + if (!(ckey in ohlcSubCounts) || ohlcSubCounts[ckey] === 0) { + console.error('overdecremented ohlc', pool, period) + ohlcSubCounts[ckey] = 1 + } else { + ohlcSubCounts[ckey]-- + if (ohlcSubCounts[key] === 0) + socket.emit('unsubOHLCs', chainId, [key]) + } + } +} + + +export function unsubAllOHLCs( chainId ) { + const chainStr = chainId.toString() + '|' + const toUnsub = [] + for( const k of Object.keys(ohlcSubCounts) ) { + if (k.startsWith(chainStr)) + toUnsub.push(k.slice(chainStr.length)) + } + if( toUnsub.length ) + socket.emit('unsubOHLCs', chainId, toUnsub ) +} + + export function subOHLCs( chainId, poolPeriods ) { if( !poolPeriods.length ) return @@ -18,9 +52,8 @@ export function subOHLCs( chainId, poolPeriods ) { if (!(key in ohlcSubCounts) || ohlcSubCounts[key] === 0) { ohlcSubCounts[key] = 1 toSub.push(key) - } else { + } else ohlcSubCounts[key]++ - } } if( toSub.length ) socket.emit('subOHLCs', chainId, toSub) @@ -33,16 +66,12 @@ export function unsubOHLCs( chainId, poolPeriods ) { if (!(key in ohlcSubCounts) || ohlcSubCounts[key] === 0) { console.error('overdecremented',pool,period) ohlcSubCounts[key] = 1 - toSub.push(key) } else { ohlcSubCounts[key]-- - if( ohlcSubCounts[key] == 0 ) + if( ohlcSubCounts[key] === 0 ) toUnsub.push(key) } } if( toUnsub.length ) socket.emit('unsubOHLCs', chainId, toUnsub ) } - -// todo get history - diff --git a/src/charts/chart.js b/src/charts/chart.js index 25cc892..6e66f17 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -1,6 +1,6 @@ import {useChartOrderStore} from "@/orderbuild.js"; import {invokeCallbacks, prototype} from "@/common.js"; -import datafeed, {lookupSymbol} from "@/charts/datafeed.js"; +import {DataFeed, initFeeDropdown, lookupSymbol} from "@/charts/datafeed.js"; export let widget = null export let chart = null @@ -47,7 +47,7 @@ export function initWidget(el) { interval: '15', container: el, // datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"), - datafeed: datafeed, // use this for ohlc + datafeed: DataFeed, // use this for ohlc locale: "en", disabled_features: [], enabled_features: ['saveload_separate_drawings_storage'], @@ -62,8 +62,8 @@ export function initWidget(el) { widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged) widget.subscribe('mouse_down', mouseDown) widget.subscribe('mouse_up', mouseUp) + widget.headerReady().then(()=>initFeeDropdown(widget)) widget.onChartReady(initChart) - } diff --git a/src/charts/datafeed.js b/src/charts/datafeed.js index 82cbec7..55ea641 100644 --- a/src/charts/datafeed.js +++ b/src/charts/datafeed.js @@ -3,10 +3,50 @@ import { unsubscribeFromStream, } from './streaming.js'; -import {jBars} from './jBars.js'; +import {jBars, tvResolutionToPeriodString} from './jBars.js'; import {metadata} from "@/version.js"; import FlexSearch from "flexsearch"; import {useChartOrderStore} from "@/orderbuild.js"; +import {useOrderStore, useStore} from "@/store/store.js"; +import {subOHLC, unsubAllOHLCs, unsubOHLC} from "@/blockchain/ohlcs.js"; + +let feeDropdown = null +let widget = null + +export function initFeeDropdown(w) { + widget = w + widget.createDropdown( + { + title: 'Fees', + tooltip: 'Choose Fee Tier', + items: [/*{title: 'Automatic Fee Selection', onSelect: () => {console.log('autofees')}}*/], + icon: ``, + } + ).then(dropdown => {feeDropdown = dropdown; updateFeeDropdown()}) +} + + +function updateFeeDropdown() { + if (feeDropdown===null) return + const symbolItem = useChartOrderStore().selectedSymbol + const feeOpts = { + items: symbolItem.pools.map((p)=> { + return { + title: (p[1]/10000).toFixed(3)+'%', + onSelect: ()=>selectPool(p), + } + }) + } + feeDropdown.applyOptions(feeOpts) +} + + +function selectPool(p) { + const co = useChartOrderStore(); + if ( co.selectedPool === null || co.selectedPool[0] !== p[0] || co.selectedPool[1] !== p[0]) { + co.selectedPool = p + } +} const lastBarsCache = new Map(); @@ -52,11 +92,17 @@ const indexes = {} const symbolsSeen = {} // keyed by (base,quote) so we only list one pool per pair even if there are many fee tiers function addSymbol(p, base, quote, inverted) { + console.log('addSymbol', p) + if (inverted) + [base, quote] = [quote,base] const symbol = base.s + '/' + quote.s const exchange = ['UNIv2', 'UNIv3'][p.e] const full_name = exchange + ':' + symbol // + '%' + formatFee(fee) if (full_name in symbolsSeen) { // add this pool's address to the existing index but don't create a new symbol + const symbolInfo = _symbols[full_name]; + symbolInfo.pools.push([p.a, p.f]) + symbolInfo.pools.sort((a,b)=>a[1]-b[1]) indexes[full_name].as.push(p.a) return } @@ -64,7 +110,8 @@ function addSymbol(p, base, quote, inverted) { const longExchange = ['Uniswap v2', 'Uniswap v3',][p.e] const description = `${base.n} / ${quote.n}` const type = 'swap' - _symbols[full_name] = {symbol, full_name, description, exchange, type, inverted, base, quote, x:p.x} + const pools = [[p.a, p.f]] + _symbols[full_name] = {symbol, full_name, description, exchange, type, inverted, base, quote, pools, x:p.x} indexes[full_name] = { // key id: full_name, @@ -97,7 +144,6 @@ async function getAllSymbols() { tokenMap[t.a] = t metadata.p.forEach((p)=>{ poolMap[p.a] = p - // todo inversion const base = tokenMap[p.b]; const quote = tokenMap[p.q]; if (!base) { @@ -122,7 +168,7 @@ export function lookupSymbol(fullName) { return _symbols[fullName] } -export default { +export const DataFeed = { onReady: (callback) => { console.log('[onReady]: Method call'); setTimeout(() => callback(configurationData)); @@ -160,12 +206,18 @@ export default { onResolveErrorCallback('cannot resolve symbol'); return; } - useChartOrderStore().selectedSymbol = symbolItem + const co = useChartOrderStore(); + const os = useOrderStore() + co.selectedSymbol = symbolItem + const pool = symbolItem.pools[Math.trunc(symbolItem.pools.length/2)]; + // noinspection JSValidateTypes + co.selectedPool = [pool] + updateFeeDropdown() // Symbol information object const symbolInfo = { ticker: symbolItem.full_name, name: symbolItem.symbol, - description: symbolItem.description, + description: symbolItem.description + ` ${(pool[1]/10000).toFixed(2)}%`, type: symbolItem.type, session: '24x7', timezone: 'Etc/UTC', @@ -180,7 +232,6 @@ export default { volume_precision: 2, data_status: 'streaming', }; - console.log('[resolveSymbol]: Symbol resolved', symbolName); onSymbolResolvedCallback(symbolInfo); }, @@ -188,11 +239,11 @@ export default { getBars: async (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => { const { from, to, firstDataRequest } = periodParams; console.log('[getBars]: Method call', symbolInfo, resolution, from, to); - - try { + // todo need to consider the selected fee tier let bars, metadata; - [bars, metadata] = await jBars(from, to, resolution); // This is the one that does all the work + const pool = useChartOrderStore().selectedPool; + [bars, metadata] = await jBars(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], @@ -215,6 +266,11 @@ export default { onResetCacheNeededCallback, ) => { console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID); + const chainId = useStore().chainId; + const poolAddr = useChartOrderStore().selectedPool[0]; + const period = tvResolutionToPeriodString(resolution); + subscriptions[subscriberUID] = [chainId, poolAddr, period] + subOHLC(chainId, poolAddr, period) return; // disable subscribeOnStream( symbolInfo, @@ -228,7 +284,12 @@ export default { unsubscribeBars: (subscriberUID) => { console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID); + const [chainId, poolAddr, period] = subscriptions[subscriberUID] + delete subscriptions[subscriberUID] + unsubOHLC(chainId, poolAddr, period) return; // disable unsubscribeFromStream(subscriberUID); }, }; + +const subscriptions = {} diff --git a/src/charts/jBars.js b/src/charts/jBars.js index 055c867..afe2c6a 100644 --- a/src/charts/jBars.js +++ b/src/charts/jBars.js @@ -1,11 +1,23 @@ -export async function jBars (from, to, res) { +import {useStore} from "@/store/store.js"; +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 resMap = {} +for (const i in file_res) { + resMap[supported_res[i]] = file_res[i] +} + +export function tvResolutionToPeriodString(res) { + return resMap[res] +} + +export async function jBars (contract, from, to, res) { console.log('[jBars]: Method call', res, from, to); - var toDate = new Date(to*1000); - - var fromDate = new Date(from*1000); - if (res=="1W") { // for 1W, day must be Sunday + const toDate = new Date(to*1000); + const fromDate = new Date(from*1000); + if (res==="1W") { // for 1W, day must be Sunday const day = fromDate.getUTCDay(); // 0<=day<7 fromDate.setDate(fromDate.getDate() + (7-day)%7 ); } @@ -30,11 +42,9 @@ export async function jBars (from, to, res) { console.log("fromDate:", fromDate.toUTCString()); console.log("toDate: ", toDate.toUTCString()); - const contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443"; + contract = "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443"; // const contract = "0xC6962004f452bE9203591991D15f6b388e09E8D0"; - 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']; @@ -59,13 +69,13 @@ export async function jBars (from, to, res) { // Fetch one sample file as needed - if (iFile == undefined ? true : - is_monthly_res ? iFile.getUTCMonth() != iDate.getUTCMonth() : - is_daily_res ? iFile.getUTCDate() != iDate.getUTCDate() : + if (iFile === undefined ? true : + is_monthly_res ? iFile.getUTCMonth() !== iDate.getUTCMonth() : + is_daily_res ? iFile.getUTCDate() !== iDate.getUTCDate() : false // is_single_res ) { - const fres = file_res[supported_res.indexOf(res)] + const fres = tvResolutionToPeriodString(res) const yr = iDate.getUTCFullYear(); const yrdir = is_single_res ? "" : `/${yr}`; const mo = String(iDate.getUTCMonth()+1).padStart(2, '0'); // January is month 0 in Date object @@ -73,6 +83,9 @@ export async function jBars (from, to, res) { const yrmo = !is_single_res ? `-${yr}${mo}` : ""; const server = "https://alpha.dexorder.trade" + // todo use correct chainId not a hardcoded 42161 + // const chainId = useStore().chainId + // let url = `${server}/ohlc/${chainId}/${contract}/${fres}${yrdir}/${contract}-${fres}${yrmo}${date}.json`; let url = `${server}/ohlc/42161/${contract}/${fres}${yrdir}/${contract}-${fres}${yrmo}${date}.json`; let response = await fetch(url); @@ -105,7 +118,7 @@ export async function jBars (from, to, res) { const insert_missing_samples = false; const visible_missing_samples = false; - if (ohlcDate == undefined) { + if (ohlcDate === undefined) { bar = { time: iDate.getTime(), } @@ -120,7 +133,7 @@ export async function jBars (from, to, res) { } // file exists, but ohlc sample not for this time, insert missing sample - else if ( iDate.getTime() != ohlcDate.getTime() ) { + else if ( iDate.getTime() !== ohlcDate.getTime() ) { bar = { time: iDate.getTime(), } @@ -145,14 +158,14 @@ export async function jBars (from, to, res) { iohlc++; } - if (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); @@ -168,7 +181,7 @@ export async function jBars (from, to, res) { // 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; + let noData = bars.length === 0; if (noData) console.log("noData == true!"); return [bars, {noData}]; } diff --git a/src/orderbuild.js b/src/orderbuild.js index 701ab50..1d4c97f 100644 --- a/src/orderbuild.js +++ b/src/orderbuild.js @@ -31,6 +31,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => { const orders = ref([]) const selectedOrder = ref(null) const selectedSymbol = ref(null) + const selectedPool = ref(null) const drawing = ref(false) const drawingCallbacks = ref(null) // only during draw mode @@ -53,7 +54,7 @@ export const useChartOrderStore = defineStore('chart_orders', () => { } return { - chartReady, selectedSymbol, orders, drawing, drawingCallbacks, newOrder, removeOrder, + chartReady, selectedSymbol, selectedPool, orders, drawing, drawingCallbacks, newOrder, removeOrder, } }) diff --git a/src/routeFinder.js b/src/routeFinder.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/socket.js b/src/socket.js index 331a816..4952e29 100644 --- a/src/socket.js +++ b/src/socket.js @@ -21,7 +21,7 @@ socket.on('p', async (chainId, pool, price) => { s.poolPrices[[chainId,pool]] = price }) -socket.on('ohlcs', async (chainId, pool, ohlcs) => { +socket.on('ohlc', async (chainId, pool, ohlcs) => { console.log('pool bars', pool, ohlcs) })