diff --git a/src/routes/options-flow/+page.svelte b/src/routes/options-flow/+page.svelte index 93f97c33..d6031c69 100644 --- a/src/routes/options-flow/+page.svelte +++ b/src/routes/options-flow/+page.svelte @@ -26,6 +26,7 @@ const allRules = { volume: { label: 'Volume', step: ['100K','10K','1K'], defaultCondition: 'over', defaultValue: '1K' }, open_interest: { label: 'Open Interest', step: ['100K','10K','1K'], defaultCondition: 'over', defaultValue: '1K' }, + 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: '50K' }, put_call: { label: 'Contract Type', step: ["Calls", "Puts"], defaultValue: 'any' }, sentiment: { label: 'Sentiment', step: ["Bullish","Neutral", "Bearish"], defaultValue: 'any' }, diff --git a/src/routes/options-flow/workers/filterWorker.ts b/src/routes/options-flow/workers/filterWorker.ts index a3ccc2d0..f21411fc 100644 --- a/src/routes/options-flow/workers/filterWorker.ts +++ b/src/routes/options-flow/workers/filterWorker.ts @@ -114,9 +114,26 @@ async function filterRawData(rawData, ruleOfList, filterQuery) { } return ruleOfList.every((rule) => { - const itemValue = item[rule.name]; - const ruleValue = convertUnitToValue(rule.value); const ruleName = rule.name.toLowerCase(); + const ruleValue = convertUnitToValue(rule.value); + + // Handle volumeOIRatio + if (ruleName === "volumeoiratio") { + const volume = parseFloat(item.volume); + const openInterest = parseFloat(item.open_interest); + + if (isNaN(volume) || isNaN(openInterest) || openInterest === 0) { + return false; // Invalid data, exclude this item + } + + const ratio = (volume / openInterest) * 100; + + if (rule.condition === "over" && ratio <= ruleValue) return false; + if (rule.condition === "under" && ratio >= ruleValue) return false; + return true; + } + + const itemValue = item[rule.name]; // Handle date_expiration if (ruleName === "date_expiration") { @@ -127,7 +144,7 @@ async function filterRawData(rawData, ruleOfList, filterQuery) { return isDateWithinRange(itemValue, ruleValue as string); } - // Handle categorical data like analyst ratings, sector, country + // Handle categorical data else if ( [ "put_call", @@ -138,28 +155,29 @@ async function filterRawData(rawData, ruleOfList, filterQuery) { ].includes(ruleName) ) { if (isAny(ruleValue)) return true; - - // Ensure itemValue is not null/undefined and is a string if (itemValue === null || itemValue === undefined) return false; const lowerItemValue = itemValue.toString().toLowerCase(); if (Array.isArray(ruleValue)) { - // Make sure ruleValue items are also treated case-insensitively return ruleValue.some( - (value) => lowerItemValue === value.toLowerCase() + (value) => lowerItemValue === value.toString().toLowerCase() ); } - // Compare case-insensitively return lowerItemValue === ruleValue.toString().toLowerCase(); } // Default numeric or string comparison if (typeof ruleValue === "string") return true; - if (itemValue === null) return false; - if (rule.condition === "over" && itemValue <= ruleValue) return false; - if (rule.condition === "under" && itemValue > ruleValue) return false; + if (itemValue === null || itemValue === undefined) return false; + const numericItemValue = parseFloat(itemValue); + if (isNaN(numericItemValue)) return false; + + if (rule.condition === "over" && numericItemValue <= ruleValue) + return false; + if (rule.condition === "under" && numericItemValue >= ruleValue) + return false; return true; }); });