From 9f462599507e4a66599b68bfb1247c9b0db1ec5a Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Sun, 3 Nov 2024 18:21:59 +0100 Subject: [PATCH] bugfixing --- src/routes/+layout.svelte | 11 +- src/routes/options-flow/+page.svelte | 3285 ++++++++++++++++---------- 2 files changed, 2003 insertions(+), 1293 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 82e1ac44..4b35dfce 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -114,7 +114,14 @@ } } - $: hideFooter = $page.url.pathname.startsWith("/community"); + $: hideFooter = + $page.url.pathname.startsWith("/options-flow") || + $page.url.pathname.startsWith("/options-zero-dte") || + $page.url.pathname.startsWith("/login") || + $page.url.pathname.startsWith("/etf") || + $page.url.pathname.startsWith("/portfolio") || + $page.url.pathname.startsWith("/hedge-funds") || + $page.url.pathname.startsWith("/community"); //$: hideSidebar = $page.url.pathname.startsWith('/contact') || $page.url.pathname.startsWith('/imprint') || $page.url.pathname.startsWith('/privacy-policy') || $page.url.pathname.startsWith('/terms-of-use') || $page.url.pathname.startsWith('/about') || $page.url.pathname.startsWith('/community/create-post') || $page.url.pathname.startsWith('/login') || $page.url.pathname.startsWith('/register') @@ -286,7 +293,7 @@ const handleTwitchMessage = (event) => { > - import {screenWidth, numberOfUnreadNotification, isOpen } from '$lib/store'; - import notifySound from '$lib/audio/options-flow-reader.mp3'; + import { screenWidth, numberOfUnreadNotification, isOpen } from "$lib/store"; + import notifySound from "$lib/audio/options-flow-reader.mp3"; //import UpgradeToPro from '$lib/components/UpgradeToPro.svelte'; - import { abbreviateNumber,cn } from '$lib/utils'; - import { onMount, onDestroy } from 'svelte'; - import toast from 'svelte-french-toast'; - import { - DateFormatter, - type DateValue, - } from "@internationalized/date"; + import { abbreviateNumber, cn } from "$lib/utils"; + import { onMount, onDestroy } from "svelte"; + import toast from "svelte-french-toast"; + import { DateFormatter, type DateValue } from "@internationalized/date"; import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import * as Popover from "$lib/components/shadcn/popover/index.js"; import { Button } from "$lib/components/shadcn/button/index.js"; import { Calendar } from "$lib/components/shadcn/calendar/index.js"; import CalendarIcon from "lucide-svelte/icons/calendar"; - - import VirtualList from 'svelte-tiny-virtual-list'; - import { writable } from 'svelte/store'; + + import VirtualList from "svelte-tiny-virtual-list"; + import { writable } from "svelte/store"; export let data; - + let optionsWatchlist = data?.getOptionsWatchlist; let ruleOfList = data?.getPredefinedCookieRuleOfList || []; let displayRules = []; let filteredData = []; - let filterQuery = ''; - let animationClass = '' - let animationId = ''; + let filterQuery = ""; + let animationClass = ""; + let animationId = ""; let socket: WebSocket | null = null; // Initialize socket as null - + let syncWorker: Worker | undefined; - let ruleName = ''; - let searchTerm = ''; + let ruleName = ""; + let searchTerm = ""; let showFilters = true; let filteredRows = []; let shouldLoadWorker = writable(false); const df = new DateFormatter("en-US", { - day: "2-digit", - month: "short", - year: "numeric" - }); + day: "2-digit", + month: "short", + year: "numeric", + }); let selectedDate: DateValue | undefined = undefined; -const allRules = { - volume: { label: 'Volume', step: ['100K','10K','1K'], defaultCondition: 'over', defaultValue: 'any' }, - open_interest: { label: 'Open Interest', step: ['100K','10K','1K'], defaultCondition: 'over', defaultValue: 'any' }, - volumeOIRatio: { label: 'Volume / Open Interest', step: ['100%','80%','60%','50%','30%','15%','10%','5%'], defaultCondition: 'over', defaultValue: 'any' }, - cost_basis: { label: 'Premium', step: ['10M','5M','1M','500K','100K','50K','10K','5K'], defaultCondition: 'over', defaultValue: 'any' }, - put_call: { label: 'Contract Type', step: ["Calls", "Puts"], defaultValue: 'any' }, - sentiment: { label: 'Sentiment', step: ["Bullish","Neutral", "Bearish"], defaultValue: 'any' }, - execution_estimate: { label: 'Execution', step: ["At Ask","At Bid", "At Midpoint", "Below Ask", "Below Bid",], defaultValue: 'any' }, - option_activity_type: { label: 'Option Type', step: ["Sweep","Trade"], defaultValue: 'any' }, - date_expiration: { label: 'Date Expiration', step: ["Same Day", "1 day","1 Week","2 Weeks","1 Month","3 Months","6 Months","1 Year","3 Years"], defaultValue: 'any' }, - underlying_type: { label: 'Asset Type', step: ["Stock", "ETF"], defaultValue: 'any' }, + const allRules = { + volume: { + label: "Volume", + step: ["100K", "10K", "1K"], + defaultCondition: "over", + defaultValue: "any", + }, + open_interest: { + label: "Open Interest", + step: ["100K", "10K", "1K"], + defaultCondition: "over", + defaultValue: "any", + }, + volumeOIRatio: { + label: "Volume / Open Interest", + step: ["100%", "80%", "60%", "50%", "30%", "15%", "10%", "5%"], + defaultCondition: "over", + defaultValue: "any", + }, + cost_basis: { + label: "Premium", + step: ["10M", "5M", "1M", "500K", "100K", "50K", "10K", "5K"], + defaultCondition: "over", + defaultValue: "any", + }, + put_call: { + label: "Contract Type", + step: ["Calls", "Puts"], + defaultValue: "any", + }, + sentiment: { + label: "Sentiment", + step: ["Bullish", "Neutral", "Bearish"], + defaultValue: "any", + }, + execution_estimate: { + label: "Execution", + step: ["At Ask", "At Bid", "At Midpoint", "Below Ask", "Below Bid"], + defaultValue: "any", + }, + option_activity_type: { + label: "Option Type", + step: ["Sweep", "Trade"], + defaultValue: "any", + }, + date_expiration: { + label: "Date Expiration", + step: [ + "Same Day", + "1 day", + "1 Week", + "2 Weeks", + "1 Month", + "3 Months", + "6 Months", + "1 Year", + "3 Years", + ], + defaultValue: "any", + }, + underlying_type: { + label: "Asset Type", + step: ["Stock", "ETF"], + defaultValue: "any", + }, + }; -}; + // Generate allRows from allRules + $: allRows = Object?.entries(allRules) + ?.sort(([, a], [, b]) => a.label.localeCompare(b.label)) // Sort by label + ?.map(([ruleName, ruleProps]) => ({ + rule: ruleName, + ...ruleProps, + })); -// Generate allRows from allRules -$: allRows = Object?.entries(allRules) - ?.sort(([, a], [, b]) => a.label.localeCompare(b.label)) // Sort by label - ?.map(([ruleName, ruleProps]) => ({ - rule: ruleName, - ...ruleProps - })); + let ruleCondition = {}; + let valueMappings = {}; -let ruleCondition = {}; -let valueMappings = {}; - -Object.keys(allRules).forEach(ruleName => { - ruleCondition[ruleName] = allRules[ruleName].defaultCondition; - valueMappings[ruleName] = allRules[ruleName].defaultValue; -}); - -// Update ruleCondition and valueMappings based on existing rules -ruleOfList.forEach(rule => { -ruleCondition[rule.name] = rule.condition || allRules[rule.name].defaultCondition; -valueMappings[rule.name] = rule.value || allRules[rule.name].defaultValue; -}); - - - -async function handleDeleteRule(state) { - for (let i = 0; i < ruleOfList.length; i++) { - if (ruleOfList[i].name === state) { - ruleOfList.splice(i, 1); // Remove the element at index i from the ruleOfList - ruleOfList = [...ruleOfList] - break; // Exit the loop after deleting the element - } - - } - - if(ruleOfList?.length === 0) - { - ruleName = ''; - } - else if (state === ruleName) - { - ruleName = ''; - } - - displayRules = allRows?.filter(row => ruleOfList.some(rule => rule.name === row.rule)); - shouldLoadWorker.set(true); - await saveCookieRuleOfList() -} - - - -async function handleResetAll() { - ruleOfList = []; - filteredData = []; - ruleOfList = [...ruleOfList]; - ruleName = ''; - filterQuery = ''; - checkedItems = new Set(); - Object.keys(allRules).forEach(ruleName => { + Object.keys(allRules).forEach((ruleName) => { ruleCondition[ruleName] = allRules[ruleName].defaultCondition; valueMappings[ruleName] = allRules[ruleName].defaultValue; }); - displayRules = allRows?.filter(row => ruleOfList.some(rule => rule.name === row.rule)); - displayedData = rawData; - await saveCookieRuleOfList() -} + // Update ruleCondition and valueMappings based on existing rules + ruleOfList.forEach((rule) => { + ruleCondition[rule.name] = + rule.condition || allRules[rule.name].defaultCondition; + valueMappings[rule.name] = rule.value || allRules[rule.name].defaultValue; + }); -function changeRule(state: string) -{ - searchTerm = ''; - ruleName = state; - handleAddRule() + async function handleDeleteRule(state) { + for (let i = 0; i < ruleOfList.length; i++) { + if (ruleOfList[i].name === state) { + ruleOfList.splice(i, 1); // Remove the element at index i from the ruleOfList + ruleOfList = [...ruleOfList]; + break; // Exit the loop after deleting the element + } + } - //const closePopup = document.getElementById("ruleModal"); - //closePopup?.dispatchEvent(new MouseEvent('click')) -} + if (ruleOfList?.length === 0) { + ruleName = ""; + } else if (state === ruleName) { + ruleName = ""; + } -function handleAddRule() { - - if (ruleName === '') { - toast.error('Please select a rule', { - style: 'border-radius: 200px; background: #333; color: #fff;' + displayRules = allRows?.filter((row) => + ruleOfList.some((rule) => rule.name === row.rule), + ); + shouldLoadWorker.set(true); + await saveCookieRuleOfList(); + } + + async function handleResetAll() { + ruleOfList = []; + filteredData = []; + ruleOfList = [...ruleOfList]; + ruleName = ""; + filterQuery = ""; + checkedItems = new Set(); + Object.keys(allRules).forEach((ruleName) => { + ruleCondition[ruleName] = allRules[ruleName].defaultCondition; + valueMappings[ruleName] = allRules[ruleName].defaultValue; + }); + displayRules = allRows?.filter((row) => + ruleOfList.some((rule) => rule.name === row.rule), + ); + displayedData = rawData; + await saveCookieRuleOfList(); + } + + function changeRule(state: string) { + searchTerm = ""; + ruleName = state; + handleAddRule(); + + //const closePopup = document.getElementById("ruleModal"); + //closePopup?.dispatchEvent(new MouseEvent('click')) + } + + function handleAddRule() { + if (ruleName === "") { + toast.error("Please select a rule", { + style: "border-radius: 200px; background: #333; color: #fff;", }); return; } - + let newRule; - - switch (ruleName) { + + switch (ruleName) { case "put_call": case "sentiment": case "execution_estimate": case "option_activity_type": case "date_expiration": case "underlying_type": - newRule = { name: ruleName, value: Array.isArray(valueMappings[ruleName]) ? valueMappings[ruleName] : [valueMappings[ruleName]] }; // Ensure value is an array + newRule = { + name: ruleName, + value: Array.isArray(valueMappings[ruleName]) + ? valueMappings[ruleName] + : [valueMappings[ruleName]], + }; // Ensure value is an array break; default: - newRule = { - name: ruleName, - condition: ruleCondition[ruleName], - value: valueMappings[ruleName] + newRule = { + name: ruleName, + condition: ruleCondition[ruleName], + value: valueMappings[ruleName], }; break; } handleRule(newRule); -} + } + async function handleRule(newRule) { + const existingRuleIndex = ruleOfList.findIndex( + (rule) => rule.name === newRule.name, + ); - -async function handleRule(newRule) { - const existingRuleIndex = ruleOfList.findIndex(rule => rule.name === newRule.name); - - if (existingRuleIndex !== -1) { - const existingRule = ruleOfList[existingRuleIndex]; - if (existingRule.name === newRule.name) { - // Remove the rule instead of showing an error - ruleOfList.splice(existingRuleIndex, 1); - ruleOfList = [...ruleOfList]; // Trigger reactivity - Object.keys(allRules).forEach(ruleName => { - ruleCondition[ruleName] = allRules[ruleName].defaultCondition; - valueMappings[ruleName] = allRules[ruleName].defaultValue; - }); - displayRules = allRows?.filter(row => ruleOfList.some(rule => rule.name === row.rule)); + if (existingRuleIndex !== -1) { + const existingRule = ruleOfList[existingRuleIndex]; + if (existingRule.name === newRule.name) { + // Remove the rule instead of showing an error + ruleOfList.splice(existingRuleIndex, 1); + ruleOfList = [...ruleOfList]; // Trigger reactivity + Object.keys(allRules).forEach((ruleName) => { + ruleCondition[ruleName] = allRules[ruleName].defaultCondition; + valueMappings[ruleName] = allRules[ruleName].defaultValue; + }); + displayRules = allRows?.filter((row) => + ruleOfList.some((rule) => rule.name === row.rule), + ); + } else { + ruleOfList[existingRuleIndex] = newRule; + ruleOfList = [...ruleOfList]; // Trigger reactivity + } } else { - ruleOfList[existingRuleIndex] = newRule; - ruleOfList = [...ruleOfList]; // Trigger reactivity - } - } else { - ruleOfList = [...ruleOfList, newRule]; - /* + ruleOfList = [...ruleOfList, newRule]; + /* toast.success('Rule added', { style: 'border-radius: 200px; background: #333; color: #fff;' }); */ - shouldLoadWorker.set(true); + shouldLoadWorker.set(true); + } } -} -const loadWorker = async () => { + const loadWorker = async () => { syncWorker.postMessage({ rawData, ruleOfList, filterQuery }); -}; + }; -const handleMessage = (event) => { - displayRules = allRows?.filter(row => ruleOfList.some(rule => rule.name === row.rule)); + const handleMessage = (event) => { + displayRules = allRows?.filter((row) => + ruleOfList.some((rule) => rule.name === row.rule), + ); filteredData = event.data?.filteredData ?? []; - displayedData = filteredData - console.log('handle Message') + displayedData = filteredData; + console.log("handle Message"); calculateStats(displayedData); //console.log(displayedData) -}; + }; + function changeRuleCondition(name: string, state: string) { + ruleName = name; + ruleCondition[ruleName] = state; + } -function changeRuleCondition(name: string, state: string) { - ruleName = name; - ruleCondition[ruleName] = state; -} + let checkedItems = new Set(ruleOfList.flatMap((rule) => rule.value)); -let checkedItems = new Set(ruleOfList.flatMap(rule => rule.value)); - -function isChecked(item) { + function isChecked(item) { return checkedItems.has(item); } -async function handleChangeValue(value) { - - - if (checkedItems.has(value)) { + async function handleChangeValue(value) { + if (checkedItems.has(value)) { checkedItems.delete(value); } else { checkedItems.add(value); } - if (["put_call","sentiment","execution_estimate","option_activity_type","date_expiration","underlying_type"]?.includes(ruleName)) { - // Ensure valueMappings[ruleName] is initialized as an array - if (!Array.isArray(valueMappings[ruleName])) { - valueMappings[ruleName] = []; // Initialize as an empty array if not already - } + if ( + [ + "put_call", + "sentiment", + "execution_estimate", + "option_activity_type", + "date_expiration", + "underlying_type", + ]?.includes(ruleName) + ) { + // Ensure valueMappings[ruleName] is initialized as an array + if (!Array.isArray(valueMappings[ruleName])) { + valueMappings[ruleName] = []; // Initialize as an empty array if not already + } - const index = valueMappings[ruleName].indexOf(value); - if (index === -1) { - // Add the country if it's not already selected - valueMappings[ruleName].push(value); + const index = valueMappings[ruleName].indexOf(value); + if (index === -1) { + // Add the country if it's not already selected + valueMappings[ruleName].push(value); + } else { + // Remove the country if it's already selected (deselect) + valueMappings[ruleName].splice(index, 1); + } + + // If no countries are selected, set value to "any" + if (valueMappings[ruleName].length === 0) { + valueMappings[ruleName] = "any"; + } + } else if (ruleName in valueMappings) { + // Handle non-country rules as single values + valueMappings[ruleName] = value; } else { - // Remove the country if it's already selected (deselect) - valueMappings[ruleName].splice(index, 1); + console.warn(`Unhandled rule: ${ruleName}`); } - // If no countries are selected, set value to "any" - if (valueMappings[ruleName].length === 0) { - valueMappings[ruleName] = "any"; - } - - } else if (ruleName in valueMappings) { - // Handle non-country rules as single values - valueMappings[ruleName] = value; - } else { - console.warn(`Unhandled rule: ${ruleName}`); - } - - const ruleToUpdate = ruleOfList?.find(rule => rule.name === ruleName); + const ruleToUpdate = ruleOfList?.find((rule) => rule.name === ruleName); if (ruleToUpdate) { ruleToUpdate.value = valueMappings[ruleToUpdate.name]; ruleToUpdate.condition = ruleCondition[ruleToUpdate.name]; @@ -267,42 +325,34 @@ async function handleChangeValue(value) { } shouldLoadWorker.set(true); - await saveCookieRuleOfList() + await saveCookieRuleOfList(); + } + const nyseDate = new Date( + data?.getOptionsFlowFeed?.at(0)?.date ?? null, + )?.toLocaleString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + timeZone: "Europe/Berlin", + }); -} - - - - - - - - - - - - - - -const nyseDate = new Date(data?.getOptionsFlowFeed?.at(0)?.date ?? null)?.toLocaleString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - timeZone: 'Europe/Berlin' -}); - -let rawData = data?.getOptionsFlowFeed?.filter(item => - Object?.values(item)?.every(value => - value !== null && value !== undefined && - (typeof value !== 'object' || Object?.values(value)?.every(subValue => subValue !== null && subValue !== undefined)) - ) -); + let rawData = data?.getOptionsFlowFeed?.filter((item) => + Object?.values(item)?.every( + (value) => + value !== null && + value !== undefined && + (typeof value !== "object" || + Object?.values(value)?.every( + (subValue) => subValue !== null && subValue !== undefined, + )), + ), + ); rawData?.forEach((item) => { - item.dte = daysLeft(item?.date_expiration); - }); - - let displayedData =[]; + item.dte = daysLeft(item?.date_expiration); + }); + + let displayedData = []; let flowSentiment; let putCallRatio; @@ -314,7 +364,7 @@ let rawData = data?.getOptionsFlowFeed?.filter(item => let highestVolumeTicker; let highestPremiumTicker; let highestOpenInterestTicker; - + let audio; let muted = false; let newIncomingData = false; @@ -323,7 +373,7 @@ let rawData = data?.getOptionsFlowFeed?.filter(item => let isLoaded = false; let mode = $isOpen === true ? true : false; let showMore = false; - + let optionSymbol; let optionDescription; let optionPremium; @@ -339,66 +389,69 @@ let rawData = data?.getOptionsFlowFeed?.filter(item => let optionPrice; let optionexecution_estimate; - - function toggleMode() - { - if ($isOpen) { - mode = !mode; - if(mode === true && selectedDate !== undefined) { - selectedDate = undefined; - rawData = data?.getOptionsFlowFeed - displayedData = [...rawData]; - shouldLoadWorker.set(true); - } + function toggleMode() { + if ($isOpen) { + mode = !mode; + if (mode === true && selectedDate !== undefined) { + selectedDate = undefined; + rawData = data?.getOptionsFlowFeed; + displayedData = [...rawData]; + shouldLoadWorker.set(true); } - else { - toast.error(`Market is closed`, { - style: 'border-radius: 200px; background: #333; color: #fff;' - }); - - } - + } else { + toast.error(`Market is closed`, { + style: "border-radius: 200px; background: #333; color: #fff;", + }); + } } - + function formatTime(timeString) { - // Split the time string into components - const [hours, minutes, seconds] = timeString.split(':').map(Number); + // Split the time string into components + const [hours, minutes, seconds] = timeString.split(":").map(Number); - // Determine AM or PM - const period = hours >= 12 ? 'PM' : 'AM'; + // Determine AM or PM + const period = hours >= 12 ? "PM" : "AM"; - // Convert hours from 24-hour to 12-hour format - const formattedHours = hours % 12 || 12; // Converts 0 to 12 for midnight + // Convert hours from 24-hour to 12-hour format + const formattedHours = hours % 12 || 12; // Converts 0 to 12 for midnight - // Format the time string - const formattedTimeString = `${formattedHours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')} ${period}`; + // Format the time string + const formattedTimeString = `${formattedHours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")} ${period}`; - return formattedTimeString; -} + return formattedTimeString; + } -function handleViewData(optionData) { - //optionStart = optionData['Start Date'] === null ? 'n/a' : new Date(optionData['Start Date'])?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' }); - optionSymbol = optionData?.option_symbol; - optionDescription = optionData?.description; - optionPremium = abbreviateNumber(optionData?.cost_basis,true); - optionExpiry = reformatDate(optionData?.date_expiration); - optionContract = optionData?.put_call; - optionType = optionData?.option_activity_type; - optionStrike = optionData?.strike_price; - optionVolume = new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(optionData?.volume); - optionSpot = optionData?.underlying_price; - optionOpenInterest = new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(optionData?.open_interest); - optionSentiment = optionData?.sentiment; - optionPrice = optionData?.price; - //optionTradeCount = optionData?.tradeCount; - optionexecution_estimate = optionData?.execution_estimate; - //optionExchange = optionData?.exchange; + function handleViewData(optionData) { + //optionStart = optionData['Start Date'] === null ? 'n/a' : new Date(optionData['Start Date'])?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' }); + optionSymbol = optionData?.option_symbol; + optionDescription = optionData?.description; + optionPremium = abbreviateNumber(optionData?.cost_basis, true); + optionExpiry = reformatDate(optionData?.date_expiration); + optionContract = optionData?.put_call; + optionType = optionData?.option_activity_type; + optionStrike = optionData?.strike_price; + optionVolume = new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(optionData?.volume); + optionSpot = optionData?.underlying_price; + optionOpenInterest = new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(optionData?.open_interest); + optionSentiment = optionData?.sentiment; + optionPrice = optionData?.price; + //optionTradeCount = optionData?.tradeCount; + optionexecution_estimate = optionData?.execution_estimate; + //optionExchange = optionData?.exchange; - const openPopup = $screenWidth < 640 ? document.getElementById("optionDetailsMobileModal") : document.getElementById("optionDetailsDesktopModal"); - openPopup?.dispatchEvent(new MouseEvent('click')) - -} -/* + const openPopup = + $screenWidth < 640 + ? document.getElementById("optionDetailsMobileModal") + : document.getElementById("optionDetailsDesktopModal"); + openPopup?.dispatchEvent(new MouseEvent("click")); + } + /* function sendMessage(message) { if (socket && socket.readyState === WebSocket.OPEN) { socket.send(message); @@ -408,137 +461,130 @@ function sendMessage(message) { } */ -async function websocketRealtimeData() { - let newData = []; - try { - socket = new WebSocket(data?.wsURL + "/options-flow-reader"); - /* + async function websocketRealtimeData() { + let newData = []; + try { + socket = new WebSocket(data?.wsURL + "/options-flow-reader"); + /* socket.addEventListener("open", () => { const ids = rawData.map(item => item.id); sendMessage(JSON.stringify({ ids })); }); */ - socket.addEventListener('message', (event) => { - previousCallVolume = displayCallVolume ?? 0; - if (mode === true) { - try { - newData = JSON.parse(event.data) ?? []; - if (newData?.length !== 0) { - newData.forEach((item) => { - item.dte = daysLeft(item?.date_expiration); - }); - if(newData?.length > rawData?.length) { - rawData = newData; - displayedData = rawData; - if(!muted) { - audio?.play(); + socket.addEventListener("message", (event) => { + previousCallVolume = displayCallVolume ?? 0; + if (mode === true) { + try { + newData = JSON.parse(event.data) ?? []; + if (newData?.length !== 0) { + newData.forEach((item) => { + item.dte = daysLeft(item?.date_expiration); + }); + if (newData?.length > rawData?.length) { + rawData = newData; + displayedData = rawData; + if (!muted) { + audio?.play(); + } } } - } - if (ruleOfList?.length !== 0 || filterQuery?.length !== 0) { - shouldLoadWorker.set(true); - } + if (ruleOfList?.length !== 0 || filterQuery?.length !== 0) { + shouldLoadWorker.set(true); + } - /* + /* if (previousCallVolume !== displayCallVolume && !muted && audio) { audio?.play(); } */ - - } catch (e) { - console.error('Error processing WebSocket message:', e); + } catch (e) { + console.error("Error processing WebSocket message:", e); + } } - } - }); + }); - socket.addEventListener('close', (event) => { - console.log('WebSocket connection closed:', event.reason); - // Handle disconnection, you might want to attempt to reconnect here + socket.addEventListener("close", (event) => { + console.log("WebSocket connection closed:", event.reason); + // Handle disconnection, you might want to attempt to reconnect here + setTimeout(() => websocketRealtimeData(), 5000); // Attempt to reconnect after 5 seconds + }); + + socket.addEventListener("error", (error) => { + console.error("WebSocket error:", error); + // Handle WebSocket errors here + }); + } catch (error) { + console.error("WebSocket connection error:", error); + // Handle connection errors here setTimeout(() => websocketRealtimeData(), 5000); // Attempt to reconnect after 5 seconds - }); - - socket.addEventListener('error', (error) => { - console.error('WebSocket error:', error); - // Handle WebSocket errors here - }); - - } catch (error) { - console.error('WebSocket connection error:', error); - // Handle connection errors here - setTimeout(() => websocketRealtimeData(), 5000); // Attempt to reconnect after 5 seconds - } -} - -function daysLeft(targetDate) { - const targetTime = new Date(targetDate).getTime(); - const currentTime = new Date().getTime(); - const difference = targetTime - currentTime; - - const millisecondsPerDay = 1000 * 60 * 60 * 24; - const daysLeft = Math?.ceil(difference / millisecondsPerDay); - - return daysLeft; -} - -async function saveCookieRuleOfList() { - const postData = { - 'ruleOfList' : ruleOfList } + } - const response = await fetch('/api/options-flow-filter-cookie', { - method: 'POST', + function daysLeft(targetDate) { + const targetTime = new Date(targetDate).getTime(); + const currentTime = new Date().getTime(); + const difference = targetTime - currentTime; + + const millisecondsPerDay = 1000 * 60 * 60 * 24; + const daysLeft = Math?.ceil(difference / millisecondsPerDay); + + return daysLeft; + } + + async function saveCookieRuleOfList() { + const postData = { + ruleOfList: ruleOfList, + }; + + const response = await fetch("/api/options-flow-filter-cookie", { + method: "POST", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, body: JSON.stringify(postData), - }); // make a POST request to the server with the FormData object -} + }); // make a POST request to the server with the FormData object + } onMount(async () => { - - displayRules = allRows?.filter(row => ruleOfList?.some(rule => rule?.name === row?.rule)); - + displayRules = allRows?.filter((row) => + ruleOfList?.some((rule) => rule?.name === row?.rule), + ); audio = new Audio(notifySound); displayedData = rawData; calculateStats(rawData); - if (!syncWorker) { - const SyncWorker = await import('./workers/filterWorker?worker'); - syncWorker = new SyncWorker.default(); - syncWorker.onmessage = handleMessage; + if (!syncWorker) { + const SyncWorker = await import("./workers/filterWorker?worker"); + syncWorker = new SyncWorker.default(); + syncWorker.onmessage = handleMessage; } shouldLoadWorker.subscribe(async (value) => { - if (value) { - isLoaded = false; - await loadWorker(); - shouldLoadWorker.set(false); // Reset after worker is loaded - isLoaded = true; - } - }); + if (value) { + isLoaded = false; + await loadWorker(); + shouldLoadWorker.set(false); // Reset after worker is loaded + isLoaded = true; + } + }); - if(ruleOfList?.length !== 0) { + if (ruleOfList?.length !== 0) { shouldLoadWorker.set(true); - console.log('initial filter') - } + console.log("initial filter"); + } isLoaded = true; - + if ($isOpen) { await websocketRealtimeData(); } - - - - }); - -onDestroy(async() => { - try { + onDestroy(async () => { + try { //socket?.send('close') socket?.close(); } catch (e) { @@ -548,68 +594,86 @@ onDestroy(async() => { audio?.pause(); audio = null; } + }); - - }) - - function reformatDate(dateString) { - return dateString.substring(5, 7) + '/' + dateString.substring(8) + '/' + dateString.substring(2, 4); + return ( + dateString.substring(5, 7) + + "/" + + dateString.substring(8) + + "/" + + dateString.substring(2, 4) + ); } - - - -function calculateStats(data) { - const { callVolumeSum, putVolumeSum, bullishCount, bearishCount, neutralCount } = data?.reduce((acc, item) => { - const volume = parseInt(item?.volume); - - if (item?.put_call === "Calls") { - acc.callVolumeSum += volume; - } else if (item?.put_call === "Puts") { - acc.putVolumeSum += volume; - } - if (item?.sentiment === "Bullish") { - acc.bullishCount += 1; - } else if (item?.sentiment === "Bearish") { - acc.bearishCount += 1; - } else if (item?.sentiment === "Neutral") { - acc.neutralCount += 1; - } - + function calculateStats(data) { + const { + callVolumeSum, + putVolumeSum, + bullishCount, + bearishCount, + neutralCount, + } = data?.reduce( + (acc, item) => { + const volume = parseInt(item?.volume); + + if (item?.put_call === "Calls") { + acc.callVolumeSum += volume; + } else if (item?.put_call === "Puts") { + acc.putVolumeSum += volume; + } + + if (item?.sentiment === "Bullish") { + acc.bullishCount += 1; + } else if (item?.sentiment === "Bearish") { + acc.bearishCount += 1; + } else if (item?.sentiment === "Neutral") { + acc.neutralCount += 1; + } + return acc; - }, { callVolumeSum: 0, putVolumeSum: 0, bullishCount: 0, bearishCount: 0, neutralCount: 0 }); + }, + { + callVolumeSum: 0, + putVolumeSum: 0, + bullishCount: 0, + bearishCount: 0, + neutralCount: 0, + }, + ); - if (bullishCount > bearishCount) { - flowSentiment = 'Bullish'; - } else if (bullishCount < bearishCount) { - flowSentiment = 'Bearish'; - } else if (neutralCount > bearishCount && neutralCount > bullishCount) { - flowSentiment = 'Neutral'; - } else { - flowSentiment = '-'; - } + if (bullishCount > bearishCount) { + flowSentiment = "Bullish"; + } else if (bullishCount < bearishCount) { + flowSentiment = "Bearish"; + } else if (neutralCount > bearishCount && neutralCount > bullishCount) { + flowSentiment = "Neutral"; + } else { + flowSentiment = "-"; + } + putCallRatio = callVolumeSum !== 0 ? putVolumeSum / callVolumeSum : 0; - putCallRatio = callVolumeSum !== 0 ? (putVolumeSum / callVolumeSum) : 0; - - callPercentage = (callVolumeSum+putVolumeSum) !== 0 ? Math.floor((callVolumeSum)/(callVolumeSum+putVolumeSum)*100) : 0; - putPercentage = (callVolumeSum+putVolumeSum) !== 0 ? (100-callPercentage) : 0; - - displayCallVolume = callVolumeSum; - displayPutVolume = putVolumeSum; + callPercentage = + callVolumeSum + putVolumeSum !== 0 + ? Math.floor((callVolumeSum / (callVolumeSum + putVolumeSum)) * 100) + : 0; + putPercentage = + callVolumeSum + putVolumeSum !== 0 ? 100 - callPercentage : 0; - mostFrequentTicker = findMostFrequentTicker(data); - highestVolumeTicker = findHighestVolume(data); - highestPremiumTicker = findHighestCostBasis(data); - highestOpenInterestTicker = findHighestOpenInterest(data); + displayCallVolume = callVolumeSum; + displayPutVolume = putVolumeSum; + mostFrequentTicker = findMostFrequentTicker(data); + highestVolumeTicker = findHighestVolume(data); + highestPremiumTicker = findHighestCostBasis(data); + highestOpenInterestTicker = findHighestOpenInterest(data); } - + function findMostFrequentTicker(data) { const tickerCountMap = new Map(); // Iterate through the data and update the count for each ticker - data?.forEach(item => { + data?.forEach((item) => { const ticker = item?.ticker; if (tickerCountMap?.has(ticker)) { tickerCountMap?.set(ticker, tickerCountMap?.get(ticker) + 1); @@ -617,10 +681,10 @@ function calculateStats(data) { tickerCountMap?.set(ticker, 1); } }); - + let maxTicker; let maxCount = -1; - + // Find the ticker with the highest count tickerCountMap?.forEach((count, ticker) => { if (count > maxCount) { @@ -628,92 +692,92 @@ function calculateStats(data) { maxTicker = ticker; } }); - + return { ticker: maxTicker, count: maxCount }; } - + function findHighestVolume(data) { let maxVolume = -1; let maxVolumeTicker = null; - + // Iterate through the data and find the ticker with the highest volume - data?.forEach(item => { + data?.forEach((item) => { const volume = parseInt(item?.volume); // Assuming volume is a string, parse it to an integer if (volume > maxVolume) { maxVolume = volume; maxVolumeTicker = item?.ticker; } }); - + return { ticker: maxVolumeTicker, volume: maxVolume }; } - + function findHighestCostBasis(data) { let maxCostBasis = -1; let maxCostBasisTicker = null; - + // Iterate through the data and find the ticker with the highest cost basis - data?.forEach(item => { + data?.forEach((item) => { if (item?.cost_basis > maxCostBasis) { maxCostBasis = item?.cost_basis; maxCostBasisTicker = item?.ticker; } }); - + return { ticker: maxCostBasisTicker, costBasis: maxCostBasis }; } - + function findHighestOpenInterest(data) { let maxOpenInterest = -1; let maxOpenInterestTicker = null; - + // Iterate through the data and find the ticker with the highest open interest - data?.forEach(item => { + data?.forEach((item) => { const openInterest = parseInt(item?.open_interest); // Assuming open interest is a string, parse it to an integer if (openInterest > maxOpenInterest) { maxOpenInterest = openInterest; maxOpenInterestTicker = item?.ticker; } }); - + return { ticker: maxOpenInterestTicker, openInterest: maxOpenInterest }; } - - -const getHistoricalFlow = async () => { - // Create a delay using setTimeout wrapped in a Promise - if(data?.user?.tier === 'Pro') { - mode = false; - isLoaded = false; + const getHistoricalFlow = async () => { + // Create a delay using setTimeout wrapped in a Promise + if (data?.user?.tier === "Pro") { + mode = false; + isLoaded = false; - displayRules = allRows?.filter(row => ruleOfList.some(rule => rule.name === row.rule)); - displayedData = []; - calculateStats(displayedData); + displayRules = allRows?.filter((row) => + ruleOfList.some((rule) => rule.name === row.rule), + ); + displayedData = []; + calculateStats(displayedData); - await new Promise(resolve => setTimeout(resolve, 300)); + await new Promise((resolve) => setTimeout(resolve, 300)); - // Make the POST request after the delay - const convertDate = new Date(df.format(selectedDate?.toDate())) - const year = convertDate?.getFullYear(); - const month = String(convertDate?.getMonth() + 1).padStart(2, '0'); - const day = String(convertDate?.getDate()).padStart(2, '0'); - - const postData = { 'selectedDate': `${year}-${month}-${day}`} + // Make the POST request after the delay + const convertDate = new Date(df.format(selectedDate?.toDate())); + const year = convertDate?.getFullYear(); + const month = String(convertDate?.getMonth() + 1).padStart(2, "0"); + const day = String(convertDate?.getDate()).padStart(2, "0"); + + const postData = { selectedDate: `${year}-${month}-${day}` }; try { - const response = await fetch('/api/options-historical-flow', { - method: 'POST', - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(postData) - }); + const response = await fetch("/api/options-historical-flow", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(postData), + }); rawData = await response?.json(); - if(rawData?.length !== 0) { + if (rawData?.length !== 0) { rawData?.forEach((item) => { - item.dte = daysLeft(item?.date_expiration); + item.dte = daysLeft(item?.date_expiration); }); shouldLoadWorker.set(true); } @@ -723,268 +787,270 @@ const getHistoricalFlow = async () => { } finally { isLoaded = true; } - - } - else { - toast.error('Only for Pro Members', { - style: 'border-radius: 200px; background: #333; color: #fff;' + } else { + toast.error("Only for Pro Members", { + style: "border-radius: 200px; background: #333; color: #fff;", }); - } - -}; - - + } + }; function handleInput(event) { filterQuery = event.target.value; setTimeout(() => { - shouldLoadWorker.set(true) - }, 0); -} - -function debounce(fn, delay) { - let timeoutId; - return function(...args) { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - fn.apply(this, args); - }, delay); - }; -} - -const debouncedHandleInput = debounce(handleInput, 300); - -async function addToWatchlist(itemId) { - - if(data?.user?.tier === 'Pro') { - try { - const postData = { - 'itemIdList': [itemId], - 'id': optionsWatchlist?.id - }; - - if (optionsWatchlist?.optionsId?.includes(itemId)) { - // Remove ticker from the watchlist. - optionsWatchlist.optionsId = optionsWatchlist?.optionsId.filter( - (item) => item !== itemId); - } else { - // Add ticker to the watchlist. - animationId = itemId - animationClass = 'heartbeat'; - const removeAnimation = () => { - animationId = ''; - animationClass = ''; - }; - optionsWatchlist.optionsId = [...optionsWatchlist?.optionsId, itemId]; - const heartbeatElement = document.getElementById(itemId); - if (heartbeatElement) { - // Only add listener if it's not already present - if (!heartbeatElement.classList.contains('animation-added')) { - heartbeatElement.addEventListener('animationend', removeAnimation); - heartbeatElement.classList.add('animation-added'); // Prevent re-adding listener - } - } - } - - - const response = await fetch('/api/update-options-watchlist', { - method: 'POST', - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(postData), - }); - - optionsWatchlist.id = await response.json(); - if (!response.ok) { - throw new Error("Network response was not ok"); - } - - } catch (error) { - console.error("An error occurred:", error); - // Handle the error appropriately (e.g., show an error message to the user) - } - } else { - toast.error('Only for Pro Members', { - style: 'border-radius: 200px; background: #333; color: #fff;' - }); - } - -} - - -$: { - if(searchTerm) - { - filteredRows = allRows?.filter((row) => row?.label?.toLowerCase()?.includes(searchTerm?.toLowerCase())); - } -} - - -$: { - if (ruleOfList) { - const ruleToUpdate = ruleOfList?.find(rule => rule?.name === ruleName); - if (ruleToUpdate) { - ruleToUpdate.value = valueMappings[ruleToUpdate.name]; - ruleToUpdate.condition = ruleCondition[ruleToUpdate.name]; - ruleOfList = [...ruleOfList]; shouldLoadWorker.set(true); - - } + }, 0); } -} + function debounce(fn, delay) { + let timeoutId; + return function (...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + fn.apply(this, args); + }, delay); + }; + } -let sortOrders = { - time: 'none', - ticker: 'none', - expiry: 'none', - dte: 'none', - strike: 'none', - callPut: 'none', - sentiment: 'none', - spot: 'none', - price: 'none', - premium: 'none', - type: 'none', - vol: 'none', - oi: 'none', - }; + const debouncedHandleInput = debounce(handleInput, 300); -// Generalized sorting function -function sortData(key) { - // Reset all other keys to 'none' except the current key - for (const k in sortOrders) { - if (k !== key) { - sortOrders[k] = 'none'; + async function addToWatchlist(itemId) { + if (data?.user?.tier === "Pro") { + try { + const postData = { + itemIdList: [itemId], + id: optionsWatchlist?.id, + }; + + if (optionsWatchlist?.optionsId?.includes(itemId)) { + // Remove ticker from the watchlist. + optionsWatchlist.optionsId = optionsWatchlist?.optionsId.filter( + (item) => item !== itemId, + ); + } else { + // Add ticker to the watchlist. + animationId = itemId; + animationClass = "heartbeat"; + const removeAnimation = () => { + animationId = ""; + animationClass = ""; + }; + optionsWatchlist.optionsId = [...optionsWatchlist?.optionsId, itemId]; + const heartbeatElement = document.getElementById(itemId); + if (heartbeatElement) { + // Only add listener if it's not already present + if (!heartbeatElement.classList.contains("animation-added")) { + heartbeatElement.addEventListener( + "animationend", + removeAnimation, + ); + heartbeatElement.classList.add("animation-added"); // Prevent re-adding listener + } + } + } + + const response = await fetch("/api/update-options-watchlist", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(postData), + }); + + optionsWatchlist.id = await response.json(); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + } catch (error) { + console.error("An error occurred:", error); + // Handle the error appropriately (e.g., show an error message to the user) + } + } else { + toast.error("Only for Pro Members", { + style: "border-radius: 200px; background: #333; color: #fff;", + }); } } - // Cycle through 'none', 'asc', 'desc' for the clicked key - const orderCycle = ['none', 'asc', 'desc']; - const currentOrderIndex = orderCycle.indexOf(sortOrders[key]); - sortOrders[key] = orderCycle[(currentOrderIndex + 1) % orderCycle.length]; - - const sortOrder = sortOrders[key]; - const originalData = filteredData?.length !== 0 ? [...filteredData] : [...rawData]; - - // Reset to original data when 'none' - if (sortOrder === 'none') { - displayedData = originalData; - return; + $: { + if (searchTerm) { + filteredRows = allRows?.filter((row) => + row?.label?.toLowerCase()?.includes(searchTerm?.toLowerCase()), + ); + } } - const compareFunctions = { - time: (a, b) => { - const timeA = new Date('1970-01-01T' + a.time).getTime(); - const timeB = new Date('1970-01-01T' + b.time).getTime(); - return sortOrder === 'asc' ? timeA - timeB : timeB - timeA; - }, - ticker: (a, b) => { - const tickerA = a.ticker.toUpperCase(); - const tickerB = b.ticker.toUpperCase(); - return sortOrder === 'asc' ? tickerA.localeCompare(tickerB) : tickerB.localeCompare(tickerA); - }, - expiry: (a, b) => { - const timeA = new Date(a.date_expiration); - const timeB = new Date(b.date_expiration); - return sortOrder === 'asc' ? timeA - timeB : timeB - timeA; - }, - dte: (a, b) => { - const timeA = new Date(a.date_expiration); - const timeB = new Date(b.date_expiration); - return sortOrder === 'asc' ? timeA - timeB : timeB - timeA; - }, - strike: (a, b) => { - const strikeA = parseFloat(a.strike_price); - const strikeB = parseFloat(b.strike_price); - return sortOrder === 'asc' ? strikeA - strikeB : strikeB - strikeA; - }, - spot: (a, b) => { - const spotA = parseFloat(a.underlying_price); - const spotB = parseFloat(b.underlying_price); - return sortOrder === 'asc' ? spotA - spotB : spotB - spotA; - }, - price: (a, b) => { - const priceA = parseFloat(a.price); - const priceB = parseFloat(b.price); - return sortOrder === 'asc' ? priceA - priceB : priceB - priceA; - }, - premium: (a, b) => { - const premiumA = parseFloat(a.cost_basis); - const premiumB = parseFloat(b.cost_basis); - return sortOrder === 'asc' ? premiumA - premiumB : premiumB - premiumA; - }, - vol: (a, b) => { - const volA = parseFloat(a.volume); - const volB = parseFloat(b.volume); - return sortOrder === 'asc' ? volA - volB : volB - volA; - }, - oi: (a, b) => { - const oiA = parseFloat(a.open_interest); - const oiB = parseFloat(b.open_interest); - return sortOrder === 'asc' ? oiA - oiB : oiB - oiA; - }, - callPut: (a, b) => { - const callPutA = a.put_call?.toUpperCase(); - const callPutB = b.put_call?.toUpperCase(); - return sortOrder === 'asc' ? callPutA.localeCompare(callPutB) : callPutB.localeCompare(callPutA); - }, - sentiment: (a, b) => { - const sentimentOrder = { 'BULLISH': 1, 'NEUTRAL': 2, 'BEARISH': 3 }; - const sentimentA = sentimentOrder[a.sentiment?.toUpperCase()] || 4; - const sentimentB = sentimentOrder[b.sentiment?.toUpperCase()] || 4; - return sortOrder === 'asc' ? sentimentA - sentimentB : sentimentB - sentimentA; - }, - type: (a, b) => { - const typeOrder = { 'SWEEP': 1, 'TRADE': 2 }; - const typeA = typeOrder[a.option_activity_type?.toUpperCase()] || 3; - const typeB = typeOrder[b.option_activity_type?.toUpperCase()] || 3; - return sortOrder === 'asc' ? typeA - typeB : typeB - typeA; - }, + $: { + if (ruleOfList) { + const ruleToUpdate = ruleOfList?.find((rule) => rule?.name === ruleName); + if (ruleToUpdate) { + ruleToUpdate.value = valueMappings[ruleToUpdate.name]; + ruleToUpdate.condition = ruleCondition[ruleToUpdate.name]; + ruleOfList = [...ruleOfList]; + shouldLoadWorker.set(true); + } + } + } + + let sortOrders = { + time: "none", + ticker: "none", + expiry: "none", + dte: "none", + strike: "none", + callPut: "none", + sentiment: "none", + spot: "none", + price: "none", + premium: "none", + type: "none", + vol: "none", + oi: "none", }; - // Sort using the appropriate comparison function - displayedData = originalData.sort(compareFunctions[key]); -} + // Generalized sorting function + function sortData(key) { + // Reset all other keys to 'none' except the current key + for (const k in sortOrders) { + if (k !== key) { + sortOrders[k] = "none"; + } + } + // Cycle through 'none', 'asc', 'desc' for the clicked key + const orderCycle = ["none", "asc", "desc"]; + const currentOrderIndex = orderCycle.indexOf(sortOrders[key]); + sortOrders[key] = orderCycle[(currentOrderIndex + 1) % orderCycle.length]; + + const sortOrder = sortOrders[key]; + const originalData = + filteredData?.length !== 0 ? [...filteredData] : [...rawData]; + + // Reset to original data when 'none' + if (sortOrder === "none") { + displayedData = originalData; + return; + } + + const compareFunctions = { + time: (a, b) => { + const timeA = new Date("1970-01-01T" + a.time).getTime(); + const timeB = new Date("1970-01-01T" + b.time).getTime(); + return sortOrder === "asc" ? timeA - timeB : timeB - timeA; + }, + ticker: (a, b) => { + const tickerA = a.ticker.toUpperCase(); + const tickerB = b.ticker.toUpperCase(); + return sortOrder === "asc" + ? tickerA.localeCompare(tickerB) + : tickerB.localeCompare(tickerA); + }, + expiry: (a, b) => { + const timeA = new Date(a.date_expiration); + const timeB = new Date(b.date_expiration); + return sortOrder === "asc" ? timeA - timeB : timeB - timeA; + }, + dte: (a, b) => { + const timeA = new Date(a.date_expiration); + const timeB = new Date(b.date_expiration); + return sortOrder === "asc" ? timeA - timeB : timeB - timeA; + }, + strike: (a, b) => { + const strikeA = parseFloat(a.strike_price); + const strikeB = parseFloat(b.strike_price); + return sortOrder === "asc" ? strikeA - strikeB : strikeB - strikeA; + }, + spot: (a, b) => { + const spotA = parseFloat(a.underlying_price); + const spotB = parseFloat(b.underlying_price); + return sortOrder === "asc" ? spotA - spotB : spotB - spotA; + }, + price: (a, b) => { + const priceA = parseFloat(a.price); + const priceB = parseFloat(b.price); + return sortOrder === "asc" ? priceA - priceB : priceB - priceA; + }, + premium: (a, b) => { + const premiumA = parseFloat(a.cost_basis); + const premiumB = parseFloat(b.cost_basis); + return sortOrder === "asc" ? premiumA - premiumB : premiumB - premiumA; + }, + vol: (a, b) => { + const volA = parseFloat(a.volume); + const volB = parseFloat(b.volume); + return sortOrder === "asc" ? volA - volB : volB - volA; + }, + oi: (a, b) => { + const oiA = parseFloat(a.open_interest); + const oiB = parseFloat(b.open_interest); + return sortOrder === "asc" ? oiA - oiB : oiB - oiA; + }, + callPut: (a, b) => { + const callPutA = a.put_call?.toUpperCase(); + const callPutB = b.put_call?.toUpperCase(); + return sortOrder === "asc" + ? callPutA.localeCompare(callPutB) + : callPutB.localeCompare(callPutA); + }, + sentiment: (a, b) => { + const sentimentOrder = { BULLISH: 1, NEUTRAL: 2, BEARISH: 3 }; + const sentimentA = sentimentOrder[a.sentiment?.toUpperCase()] || 4; + const sentimentB = sentimentOrder[b.sentiment?.toUpperCase()] || 4; + return sortOrder === "asc" + ? sentimentA - sentimentB + : sentimentB - sentimentA; + }, + type: (a, b) => { + const typeOrder = { SWEEP: 1, TRADE: 2 }; + const typeA = typeOrder[a.option_activity_type?.toUpperCase()] || 3; + const typeB = typeOrder[b.option_activity_type?.toUpperCase()] || 3; + return sortOrder === "asc" ? typeA - typeB : typeB - typeA; + }, + }; + + // Sort using the appropriate comparison function + displayedData = originalData.sort(compareFunctions[key]); + } - - - - + + - - + + - {$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ''} Options Flow Feed · stocknear + {$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ""} Options + Flow Feed · stocknear - - + + - - - + + + - + - - - + + + - - - + + - - - -
- - -
- - {#if !$isOpen} -
- Live flow of {data?.user?.tier === 'Pro' && selectedDate ? df.format(selectedDate?.toDate()) : nyseDate} (NYSE Time) + {#if !$isOpen} +
+ Live flow of {data?.user?.tier === "Pro" && selectedDate + ? df.format(selectedDate?.toDate()) + : nyseDate} (NYSE Time) +
+ {/if} + +
+
+
+ + + + {$isOpen ? "Paused" : "Market Closed"} + + + + +
+ + Live Flow + +
+
+ +
+
+
+ + {#if notFound === true} + + No Results Found + + {/if} +
+ + + + + + + + + +
+
+
+ +
+ +
+ + {#if showFilters} +
+ + + {#if ruleOfList?.length !== 0} + + {/if} +
+ +
+ {#each displayRules as row (row?.rule)} + +
+
+ {row?.label?.replace("[%]", "")} +
+
+ +
+
(ruleName = row?.rule)}> + + + + + + {#if !["put_call", "sentiment", "execution_estimate", "option_activity_type", "date_expiration", "underlying_type"]?.includes(row?.rule)} + +
+
+ + +
+
+
+ {:else} + + {/if} + + {#if !["put_call", "sentiment", "execution_estimate", "option_activity_type", "date_expiration", "underlying_type"]?.includes(row?.rule)} + {#each row?.step as newValue} + + + + {/each} + {:else if ["put_call", "sentiment", "execution_estimate", "option_activity_type", "date_expiration", "underlying_type"]?.includes(row?.rule)} + {#each row?.step as item} + +
+ event.preventDefault()} + > + +
+
+ {/each} + {/if} +
+
+
+
+
+
+
+ + {/each}
{/if} +
- - - - - - -
-
-
- - - - - {$isOpen ? 'Paused' : 'Market Closed'} - - - - - - -
- - Live Flow - -
- - -
- -
-
- - -
- - {#if notFound === true} - - No Results Found - - {/if} -
- - - - - - - - - - - -
-
- -
- - -
- -
- - {#if showFilters} -
- - - {#if ruleOfList?.length !== 0} - - {/if} - -
- - -
- {#each displayRules as row (row?.rule)} - -
-
- {row?.label?.replace('[%]','')} -
-
- -
-
ruleName = row?.rule}> - - - - - - {#if !['put_call',"sentiment", "execution_estimate","option_activity_type","date_expiration","underlying_type"]?.includes(row?.rule)} - -
-
- - -
-
-
- {:else} - - {/if} - - {#if !['put_call',"sentiment", "execution_estimate","option_activity_type","date_expiration","underlying_type"]?.includes(row?.rule)} - {#each row?.step as newValue} - - - - - {/each} - {:else if ['put_call',"sentiment", "execution_estimate","option_activity_type","date_expiration","underlying_type"]?.includes(row?.rule)} - {#each row?.step as item} - -
event.preventDefault()}> - -
-
- {/each} - - {/if} -
-
-
-
-
-
-
- - {/each} -
- - {/if} - -
- - - - - - {#if isLoaded } - - -
- - -
- - - -
-
- Flow Sentiment - {flowSentiment} -
- -
- - -
-
- Put/Call - - {putCallRatio?.toFixed(3)} - -
- -
- - - - - - =1 ? 0 : 100-(putCallRatio*100)?.toFixed(2)}> - - - -
- {putCallRatio?.toFixed(2)} -
-
- - -
- - -
+ {#if isLoaded} +
+
+ +
- Call Flow - - {new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 0 - }).format(displayCallVolume)} - + Flow Sentiment + {flowSentiment}
- -
- +
+ + +
+
+ Put/Call + + {putCallRatio?.toFixed(3)} + +
+ +
+ - + - + = 1 + ? 0 + : 100 - (putCallRatio * 100)?.toFixed(2)} + > -
- {callPercentage}% +
+ {putCallRatio?.toFixed(2)} +
+
+ +
+ + +
+
+ Call Flow + + {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(displayCallVolume)} + +
+ +
+ + + + + + + + + +
+ {callPercentage}%
- -
+ +
- Put Flow - - {new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 0 + Put Flow + + {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, }).format(displayPutVolume)} - +
- -
- + +
+ - + - + -
- {putPercentage}% +
+ {putPercentage}%
@@ -1389,316 +1721,696 @@ function sortData(key) { {/if} --> - -
- - - + - - - - - - -
- {#if displayedData?.length !== 0} -
-
-
- + + +
+ {#if displayedData?.length !== 0} +
+
+
+ +
+ +
sortData("time")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Time + +
+
sortData("ticker")} + class="td cursor-pointer select-none bg-[#161618] font-bold text-slate-300 text-xs text-start uppercase" + > + Symbol + +
+
+ Save +
+
sortData("expiry")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Expiry + +
+
sortData("dte")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + DTE + +
+
sortData("strike")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Strike + +
+
sortData("callPut")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + C/P + +
+
sortData("sentiment")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Sent. + +
+
sortData("spot")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Spot + +
+
sortData("price")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Price + +
+
sortData("premium")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Prem + +
+
sortData("type")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Type + +
+
sortData("vol")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + Vol + +
+
sortData("oi")} + class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase" + > + OI + +
+
+ +
handleViewData(displayedData[index])} + slot="item" + let:index + let:style + {style} + class="tr cursor-pointer {index % 2 === 0 + ? 'bg-[#27272A]' + : 'bg-[#09090B]'}" > -
- -
sortData('time')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Time - -
-
sortData('ticker')} class="td cursor-pointer select-none bg-[#161618] font-bold text-slate-300 text-xs text-start uppercase"> - Symbol - -
-
- Save -
-
sortData('expiry')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Expiry - -
-
sortData('dte')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - DTE - -
-
sortData('strike')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Strike - -
-
sortData('callPut')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - C/P - -
-
sortData('sentiment')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Sent. - -
-
sortData('spot')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Spot - -
-
sortData('price')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Price - -
-
sortData('premium')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Prem - -
-
sortData('type')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Type - -
-
sortData('vol')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - Vol - -
-
sortData('oi')} class="td cursor-pointer select-none bg-[#161618] text-slate-300 font-bold text-xs text-start uppercase"> - OI - -
+ + +
+ {formatTime(displayedData[index]?.time)} +
+ + {displayedData[index]?.ticker} + + +
+ addToWatchlist(displayedData[index]?.id)} + style="justify-content: center;" + class="td {optionsWatchlist.optionsId?.includes( + displayedData[index]?.id, + ) + ? 'text-[#FBCE3C]' + : 'text-white'}" + > +
handleViewData(displayedData[index])} - slot="item" - let:index - let:style - {style} - class="tr cursor-pointer {index % 2 === 0 ? 'bg-[#27272A]' : 'bg-[#09090B]'}" + style="justify-content: center;" + class="td text-sm sm:text-[1rem] text-white text-start" > - - -
- {formatTime(displayedData[index]?.time)} - -
- - {displayedData[index]?.ticker} - - -
addToWatchlist(displayedData[index]?.id)} style="justify-content: center;" class="td {optionsWatchlist.optionsId?.includes(displayedData[index]?.id) ? 'text-[#FBCE3C]' : 'text-white'}"> - -
- -
- {reformatDate(displayedData[index]?.date_expiration)} -
- -
- {displayedData[index]?.dte < 0 ? 'expired' : displayedData[index]?.dte + 'd'} -
- -
- {displayedData[index]?.strike_price} -
- -
- {displayedData[index]?.put_call} -
- -
- {displayedData[index]?.sentiment} -
- -
- {displayedData[index]?.underlying_price} -
- -
- {displayedData[index]?.price} -
- -
- {abbreviateNumber(displayedData[index]?.cost_basis)} -
- -
- {displayedData[index]?.option_activity_type} -
- -
- {new Intl.NumberFormat('en', { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(displayedData[index]?.volume)} -
- -
- {new Intl.NumberFormat('en', { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(displayedData[index]?.open_interest)} -
+ {reformatDate(displayedData[index]?.date_expiration)}
- -
+ +
+ {displayedData[index]?.dte < 0 + ? "expired" + : displayedData[index]?.dte + "d"} +
+ +
+ {displayedData[index]?.strike_price} +
+ +
+ {displayedData[index]?.put_call} +
+ +
+ {displayedData[index]?.sentiment} +
+ +
+ {displayedData[index]?.underlying_price} +
+ +
+ {displayedData[index]?.price} +
+ +
+ {abbreviateNumber(displayedData[index]?.cost_basis)} +
+ +
+ {displayedData[index]?.option_activity_type} +
+ +
+ {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(displayedData[index]?.volume)} +
+ +
+ {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(displayedData[index]?.open_interest)} +
+
+
- {:else} -
- - Looks like your taste is one-of-a-kind! No matches found... yet! -
- {/if} - -
+
+ {:else} +
+ + Looks like your taste is one-of-a-kind! No matches found... yet! +
+ {/if} +
- - - - {:else} -
-
-
- - {/if} -
- + {/if} +
- - - - - - - - - - + + - - - - - - -