From 2026bb5ed0b851ef70202199da303ee736b748ee Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Fri, 13 Dec 2024 21:23:47 +0100 Subject: [PATCH] add repeated flow --- src/routes/options-flow/+page.svelte | 33 +++++++----- .../options-flow/workers/filterWorker.ts | 54 ++++++++++++++++--- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/routes/options-flow/+page.svelte b/src/routes/options-flow/+page.svelte index b6a2ca35..fcb242d7 100644 --- a/src/routes/options-flow/+page.svelte +++ b/src/routes/options-flow/+page.svelte @@ -95,6 +95,11 @@ step: ["ITM", "OTM"], defaultValue: "any", }, + flowType: { + label: "Flow Type", + step: ["Repeated Flow"], + defaultValue: "any", + }, put_call: { label: "Contract Type", step: ["Calls", "Puts"], @@ -128,6 +133,16 @@ }, }; + const categoricalRules = [ + "moneyness", + "flowType", + "put_call", + "sentiment", + "execution_estimate", + "option_activity_type", + "underlying_type", + ]; + // Generate allRows from allRules $: allRows = Object?.entries(allRules) ?.sort(([, a], [, b]) => a.label.localeCompare(b.label)) // Sort by label @@ -218,6 +233,7 @@ switch (ruleName) { case "moneyness": + case "flowType": case "put_call": case "sentiment": case "execution_estimate": @@ -347,16 +363,7 @@ } // Specific rule handling for options-related rules - if ( - [ - "moneyness", - "put_call", - "sentiment", - "execution_estimate", - "option_activity_type", - "underlying_type", - ]?.includes(ruleName) - ) { + if (categoricalRules?.includes(ruleName)) { // Ensure valueMappings[ruleName] is initialized as an array if (!Array.isArray(valueMappings[ruleName])) { valueMappings[ruleName] = []; @@ -1130,7 +1137,7 @@ - {#if !["moneyness", "put_call", "sentiment", "execution_estimate", "option_activity_type", "underlying_type"]?.includes(row?.rule)} + {#if !categoricalRules?.includes(row?.rule)} @@ -1290,7 +1297,7 @@ > {/if} - {#if !["moneyness", "put_call", "sentiment", "execution_estimate", "option_activity_type", "underlying_type"]?.includes(row?.rule)} + {#if !categoricalRules?.includes(row?.rule)} {#each row?.step as newValue, index} {#if ruleCondition[row?.rule] === "between"} {#if newValue && row?.step[index + 1]} @@ -1334,7 +1341,7 @@ {/if} {/each} - {:else if ["moneyness", "put_call", "sentiment", "execution_estimate", "option_activity_type", "underlying_type"]?.includes(row?.rule)} + {:else if categoricalRules?.includes(row?.rule)} {#each row?.step as item} ; +} + const categoricalFields = [ 'put_call', 'sentiment', @@ -36,6 +40,7 @@ function convertUnitToValue( "etf", "itm", "otm", + "repeated flow" ]); if (nonNumericValues.has(lowerInput)) return input; if (input.endsWith("%")) { @@ -72,10 +77,38 @@ function createRuleCheck(rule, ruleName, ruleValue) { const now = new Date(new Date().toLocaleString("en-US", { timeZone: "America/New_York" })); +if (ruleName === 'flowtype') { + return (item: any, context: FilterContext = {}) => { + // Check for 'any' rule or other non-repeated flow conditions + if (ruleValue === 'any' || ruleValue?.includes("any")) { + return true; + } + + // Handle Repeated Flow logic + if (ruleValue?.includes('Repeated Flow')) { + // Initialize flowTypeCache if it doesn't exist + context.flowTypeCache = context.flowTypeCache || new Map(); + + // Create a unique key for repeated flow based on item characteristics + const key = `${item.ticker}-${item.put_call}-${item.strike_price}-${item.date_expiration}`; + + // Increment the count for the key in the flowTypeCache + const currentCount = (context.flowTypeCache.get(key) || 0) + 1; + context.flowTypeCache.set(key, currentCount); + + // Return true if this flow appears more than N times (3 in this case) + return currentCount > 3; + } + + // Fallback for other flow type conditions (i.e., non-repeated flow) + return true; + }; +} + + if (ruleName === 'moneyness') { return (item) => { - - if (ruleValue === 'any') return true; + if (ruleValue === 'any' || ruleValue?.includes("any")) return true; const currentPrice = parseFloat(item?.underlying_price); const strikePrice = parseFloat(item?.strike_price); @@ -274,17 +307,22 @@ return (item) => { async function filterRawData(rawData, ruleOfList, filterQuery) { // Early return for empty inputs - if (!rawData?.length ) { + if (!rawData?.length) { return rawData || []; } // Preprocess filter tickers const filterTickers = filterQuery - ? filterQuery.split(",").map((ticker) => ticker.trim().toUpperCase()) + ? filterQuery?.split(",").map((ticker) => ticker.trim().toUpperCase()) : []; + // Initialize context with optional flowTypeCache + const context: FilterContext = { + flowTypeCache: new Map() + }; + // Precompile rules for more efficient filtering - const compiledRules = ruleOfList.map(rule => { + const compiledRules = ruleOfList?.map(rule => { const ruleName = rule?.name?.toLowerCase(); const ruleValue = convertUnitToValue(rule.value); @@ -302,12 +340,12 @@ async function filterRawData(rawData, ruleOfList, filterQuery) { return false; } - // Apply all precompiled rules - return compiledRules.every(rule => rule?.compiledCheck(item)); + // Apply all precompiled rules, passing the context + return compiledRules?.every(rule => rule?.compiledCheck(item, context)); }); } -// Web Worker message handler +// Web Worker message handler remains the same onmessage = async (event: MessageEvent) => { const { rawData, ruleOfList, filterQuery } = event.data || {}; // Filter the data