From c8e88d33d3a1b734adabec957736c6dc786c1f04 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Tue, 23 Jul 2024 22:06:51 +0200 Subject: [PATCH] update options table with virtual list for performance boost --- package-lock.json | 6 + package.json | 1 + src/routes/options-flow/+page.svelte | 299 +++++++++++++++------------ 3 files changed, 169 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4c1c1a7..157e7239 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "svelte-range-slider-pips": "^2.3.1", "svelte-sonner": "^0.3.27", "svelte-tags-input": "^6.0.0", + "svelte-tiny-virtual-list": "^2.1.2", "tailwind-merge": "^2.4.0", "tailwind-variants": "^0.2.1", "tslib": "^2.6.2", @@ -7640,6 +7641,11 @@ "resolved": "https://registry.npmjs.org/svelte-tags-input/-/svelte-tags-input-6.0.1.tgz", "integrity": "sha512-3X5qomFSXe6E8H7Lq0oce7096tr6u06pWvTNgNoeNVIXCrRLxRYOk4Ujkte7z5WaAit47EUsZZP1TSJ2HR9ixA==" }, + "node_modules/svelte-tiny-virtual-list": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svelte-tiny-virtual-list/-/svelte-tiny-virtual-list-2.1.2.tgz", + "integrity": "sha512-jeP/WMvgFUR4mYXHGPiCexjX5DuzSO+3xzHNhxfcsFyy+uYPtnqI5UGb383swpzQAyXB0OBqYfzpYihD/5gxnA==" + }, "node_modules/svelte-writable-derived": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.1.tgz", diff --git a/package.json b/package.json index 38f085e0..81922428 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "svelte-range-slider-pips": "^2.3.1", "svelte-sonner": "^0.3.27", "svelte-tags-input": "^6.0.0", + "svelte-tiny-virtual-list": "^2.1.2", "tailwind-merge": "^2.4.0", "tailwind-variants": "^0.2.1", "tslib": "^2.6.2", diff --git a/src/routes/options-flow/+page.svelte b/src/routes/options-flow/+page.svelte index cbb7a8ac..6213c373 100644 --- a/src/routes/options-flow/+page.svelte +++ b/src/routes/options-flow/+page.svelte @@ -6,7 +6,7 @@ import { abbreviateNumber } from '$lib/utils'; import { onMount, onDestroy } from 'svelte'; import toast from 'svelte-french-toast'; - + import VirtualList from 'svelte-tiny-virtual-list'; export let data; @@ -22,7 +22,6 @@ } }); - let optionList = [] let rawData = []; let filterList = []; @@ -159,11 +158,9 @@ function handleViewData(optionData) { rawData = listFilteredData; } - // Update optionList and notFound status if (rawData?.length !== 0 && newIncomingData === true) { notFound = false; newIncomingData = false; - optionList = rawData?.slice(0, 50); } else if (!newIncomingData) { notFound = false; newIncomingData = false; @@ -171,7 +168,6 @@ function handleViewData(optionData) { notFound = true; newIncomingData = false; rawData = data?.getOptionsFlowFeed ?? []; - optionList = []; } calculateStats(rawData); @@ -206,7 +202,6 @@ function handleViewData(optionData) { onMount(async () => { audio = new Audio(notifySound); rawData = data?.getOptionsFlowFeed; - optionList = rawData?.slice(0, 100); calculateStats(rawData); isLoaded = true; @@ -214,32 +209,11 @@ function handleViewData(optionData) { await websocketRealtimeData(); } - if (data?.user?.tier === 'Pro') { - const attachScrollListener = () => { - if (scrollContainer) { - scrollContainer.addEventListener('scroll', handleScroll); - return true; - } - return false; - }; - if (!attachScrollListener()) { - const observer = new MutationObserver(() => { - if (attachScrollListener()) { - observer.disconnect(); - } - }); - - observer.observe(document.body, { childList: true, subtree: true }); - } - } }); onDestroy(async() => { - if (scrollContainer && data?.user?.tier === 'Pro') { - scrollContainer.removeEventListener('scroll', handleScroll); - }; if (typeof window !== 'undefined') { @@ -252,18 +226,7 @@ onDestroy(async() => { }) - async function handleScroll() { - if (!scrollContainer) return; - const scrollThreshold = scrollContainer.scrollHeight * 0.8; // 80% of the div height - const isBottom = scrollContainer.scrollTop + scrollContainer.clientHeight >= scrollThreshold; - if (isBottom && optionList?.length !== rawData?.length) { - const nextIndex = optionList?.length; - const filteredNewResults = rawData?.slice(nextIndex, nextIndex + 25); - optionList = [...optionList, ...filteredNewResults]; - } - } - async function assetSelector(symbol, assetType) { @@ -287,8 +250,8 @@ onDestroy(async() => { -function calculateStats(optionList) { - const { callVolumeSum, putVolumeSum, bullishCount, bearishCount } = optionList?.reduce((acc, item) => { +function calculateStats(data) { + const { callVolumeSum, putVolumeSum, bullishCount, bearishCount } = data?.reduce((acc, item) => { const volume = parseInt(item?.volume); if (item?.put_call === "Calls") { @@ -414,19 +377,15 @@ function handleInput(event) { if (newData?.length !== 0) { rawData = newData; - optionList = [...rawData?.slice(0, 100)]; - notFound = false; } else { notFound = true; rawData = data?.getOptionsFlowFeed; - optionList = rawData?.slice(0, 100); } } else { notFound = false; rawData = data?.getOptionsFlowFeed; - optionList = rawData?.slice(0, 100); } calculateStats(rawData); @@ -487,24 +446,23 @@ $: { const newData = filterExpiringSoon(rawData, Math.max(...filterList)); if (newData?.length !== 0) { rawData = newData; - optionList = rawData?.slice(0, 50); notFound = false; } else { notFound = true; rawData = data?.getOptionsFlowFeed; - optionList = []; } } else if (filterQuery?.length === 0) { rawData = data?.getOptionsFlowFeed; - optionList = rawData?.slice(0,100); } calculateStats(rawData); } } + + @@ -551,7 +509,7 @@ $: { {#if !$isOpen}
- Live flow of {new Date(optionList?.at(0)?.date ?? null)?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' })} (NYSE Time) + Live flow of {new Date(rawData?.at(0)?.date ?? null)?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' })} (NYSE Time)
{/if} @@ -829,95 +787,92 @@ $: { -
- - - - - - - - - - - - - - - - - - - {#each optionList as item,index} - - handleViewData(item)} class="w-full odd:bg-[#27272A] cursor-pointer {index+1 === optionList?.length && data?.user?.tier !== 'Pro' ? 'opacity-[0.1]' : ''}"> - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
+ +
+
Time
+
+
Expiry
+
Strike
+
C/P
+
Sent.
+
Spot
+
Price
+
Prem.
+
Type
+
Vol
+
OI
+ - - - - - - {/each} - -
TimeSymbolExpiryStrikeC/PSent.SpotPricePrem.TypeVolOI
- {formatTime(item?.time)} - assetSelector(item?.ticker, item?.assetType)} class="{index % 2 ? 'bg-[#09090B]' : 'bg-[#27272A]'} text-blue-400 text-start font-normal"> - {item?.ticker} - - {reformatDate(item?.date_expiration)} - - {item?.strike_price} - - {item?.put_call} - - {item?.sentiment} - - {item?.underlying_price} - - {item?.price} - - {abbreviateNumber(item?.cost_basis)} - - {item?.type} - - {new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 0 - }).format(item?.volume)} - - {new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 0 - }).format(item?.open_interest)} - Symbol
- +
handleViewData(rawData[index])} slot="item" let:index let:style {style} class="tr cursor-pointer"> + + +
+ {formatTime(rawData[index]?.time)} +
+ +
assetSelector(rawData[index]?.ticker, rawData[index]?.assetType)} style="justify-content: center;" class="td text-sm text-blue-400 font-normal"> + {rawData[index]?.ticker} +
+ +
+ {reformatDate(rawData[index]?.date_expiration)} +
+ +
+ {rawData[index]?.strike_price} +
+ +
+ {rawData[index]?.put_call} +
+ +
+ {rawData[index]?.sentiment} +
+ +
+ {rawData[index]?.underlying_price} +
+ +
+ {rawData[index]?.price} +
+ +
+ {abbreviateNumber(rawData[index]?.cost_basis)} +
+ +
+ {rawData[index]?.type} +
+ +
+ {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(rawData[index]?.volume)} +
+ +
+ {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0 + }).format(rawData[index]?.open_interest)} +
+ +
+ +
+ + @@ -1044,7 +999,7 @@ $: { -