bugfixing
This commit is contained in:
parent
b900b009c8
commit
955e6d942b
@ -95,7 +95,7 @@
|
||||
},
|
||||
execution_estimate: {
|
||||
label: "Execution",
|
||||
step: ["At Ask", "At Bid", "At Midpoint", "Above Ask", "Below Bid"],
|
||||
step: ["Above Ask", "Below Bid", "At Ask", "At Bid", "At Midpoint"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
option_activity_type: {
|
||||
@ -105,17 +105,8 @@
|
||||
},
|
||||
date_expiration: {
|
||||
label: "Date Expiration",
|
||||
step: [
|
||||
"Same Day",
|
||||
"1 day",
|
||||
"1 Week",
|
||||
"2 Weeks",
|
||||
"1 Month",
|
||||
"3 Months",
|
||||
"6 Months",
|
||||
"1 Year",
|
||||
"3 Years",
|
||||
],
|
||||
step: ["250", "180", "100", "80", "60", "50", "30", "20", "10", "5", "0"],
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
underlying_type: {
|
||||
@ -218,7 +209,6 @@
|
||||
case "sentiment":
|
||||
case "execution_estimate":
|
||||
case "option_activity_type":
|
||||
case "date_expiration":
|
||||
case "underlying_type":
|
||||
newRule = {
|
||||
name: ruleName,
|
||||
@ -350,7 +340,6 @@
|
||||
"sentiment",
|
||||
"execution_estimate",
|
||||
"option_activity_type",
|
||||
"date_expiration",
|
||||
"underlying_type",
|
||||
]?.includes(ruleName)
|
||||
) {
|
||||
@ -1135,7 +1124,7 @@ function sendMessage(message) {
|
||||
<DropdownMenu.Content
|
||||
class="w-64 min-h-auto max-h-72 overflow-y-auto scroller"
|
||||
>
|
||||
{#if !["put_call", "sentiment", "execution_estimate", "option_activity_type", "date_expiration", "underlying_type"]?.includes(row?.rule)}
|
||||
{#if !["put_call", "sentiment", "execution_estimate", "option_activity_type", "underlying_type"]?.includes(row?.rule)}
|
||||
<DropdownMenu.Label
|
||||
class="absolute mt-2 h-11 border-gray-800 border-b -top-1 z-20 fixed sticky bg-[#09090B]"
|
||||
>
|
||||
@ -1295,7 +1284,7 @@ function sendMessage(message) {
|
||||
></div>
|
||||
{/if}
|
||||
<DropdownMenu.Group class="min-h-10 mt-2">
|
||||
{#if !["put_call", "sentiment", "execution_estimate", "option_activity_type", "date_expiration", "underlying_type"]?.includes(row?.rule)}
|
||||
{#if !["put_call", "sentiment", "execution_estimate", "option_activity_type", "underlying_type"]?.includes(row?.rule)}
|
||||
{#each row?.step as newValue, index}
|
||||
{#if ruleCondition[row?.rule] === "between"}
|
||||
{#if newValue && row?.step[index + 1]}
|
||||
@ -1339,7 +1328,7 @@ function sendMessage(message) {
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if ["put_call", "sentiment", "execution_estimate", "option_activity_type", "date_expiration", "underlying_type"]?.includes(row?.rule)}
|
||||
{:else if ["put_call", "sentiment", "execution_estimate", "option_activity_type", "underlying_type"]?.includes(row?.rule)}
|
||||
{#each row?.step as item}
|
||||
<DropdownMenu.Item
|
||||
class="sm:hover:bg-[#2A2E39]"
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
|
||||
const categoricalFields = [
|
||||
'put_call',
|
||||
'sentiment',
|
||||
'execution_estimate',
|
||||
'option_activity_type',
|
||||
'underlying_type'
|
||||
];
|
||||
|
||||
function convertUnitToValue(
|
||||
input: string | number | string[],
|
||||
): number | string[] | string {
|
||||
@ -9,7 +18,6 @@ function convertUnitToValue(
|
||||
);
|
||||
}
|
||||
const lowerInput = input.toLowerCase();
|
||||
// Pre-compute the set for quick lookups
|
||||
const nonNumericValues = new Set([
|
||||
"any",
|
||||
"puts",
|
||||
@ -26,16 +34,6 @@ function convertUnitToValue(
|
||||
"trade",
|
||||
"stock",
|
||||
"etf",
|
||||
...[
|
||||
"1 day",
|
||||
"1 Week",
|
||||
"2 Weeks",
|
||||
"1 Month",
|
||||
"3 Months",
|
||||
"6 Months",
|
||||
"1 Year",
|
||||
"3 Years",
|
||||
],
|
||||
]);
|
||||
if (nonNumericValues.has(lowerInput)) return input;
|
||||
if (input.endsWith("%")) {
|
||||
@ -66,114 +64,13 @@ function isAny(value: string | string[]): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSameDay(date1: Date, date2: Date): boolean {
|
||||
return (
|
||||
date1.getFullYear() === date2.getFullYear() &&
|
||||
date1.getMonth() === date2.getMonth() &&
|
||||
date1.getDate() === date2.getDate()
|
||||
);
|
||||
}
|
||||
|
||||
function isDateWithinRange(dateString: string, range: string): boolean {
|
||||
let now = new Date();
|
||||
const expirationDate = new Date(dateString);
|
||||
const dayOfWeek = now.getDay();
|
||||
|
||||
// Special handling for "1 day" range when it's Friday, Saturday, or Sunday
|
||||
if (
|
||||
range.toLowerCase() === "1 day" &&
|
||||
(dayOfWeek === 5 || dayOfWeek === 6 || dayOfWeek === 0)
|
||||
) {
|
||||
// Calculate next Monday
|
||||
const daysUntilMonday =
|
||||
dayOfWeek === 5
|
||||
? 3 // From Friday
|
||||
: dayOfWeek === 6
|
||||
? 2 // From Saturday
|
||||
: 1; // From Sunday
|
||||
|
||||
const monday = new Date(now);
|
||||
monday.setDate(now.getDate() + daysUntilMonday);
|
||||
|
||||
// Compare with next Monday
|
||||
return isSameDay(expirationDate, monday);
|
||||
}
|
||||
|
||||
// Adjust now to Friday if it falls on a weekend (for other ranges)
|
||||
if (dayOfWeek === 6) {
|
||||
// Saturday
|
||||
now.setDate(now.getDate() - 1); // Move to Friday
|
||||
} else if (dayOfWeek === 0) {
|
||||
// Sunday
|
||||
now.setDate(now.getDate() - 2); // Move to Friday
|
||||
}
|
||||
|
||||
const timeDiff = expirationDate.getTime() - now.getTime();
|
||||
const daysDiff = timeDiff / (1000 * 60 * 60 * 24);
|
||||
|
||||
switch (range.toLowerCase()) {
|
||||
case "same day":
|
||||
return isSameDay(now, expirationDate);
|
||||
case "1 day":
|
||||
return daysDiff >= 0 && daysDiff <= 1;
|
||||
case "1 week":
|
||||
return daysDiff >= 0 && daysDiff <= 7;
|
||||
case "2 weeks":
|
||||
return daysDiff >= 0 && daysDiff <= 14;
|
||||
case "1 month":
|
||||
return daysDiff >= 0 && daysDiff <= 30;
|
||||
case "3 months":
|
||||
return daysDiff >= 0 && daysDiff <= 90;
|
||||
case "6 months":
|
||||
return daysDiff >= 0 && daysDiff <= 180;
|
||||
case "1 year":
|
||||
return daysDiff >= 0 && daysDiff <= 365;
|
||||
case "3 years":
|
||||
return daysDiff >= 0 && daysDiff <= 1095;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function filterRawData(rawData, ruleOfList, filterQuery) {
|
||||
// Early return for empty inputs
|
||||
if (!rawData?.length ) {
|
||||
return rawData || [];
|
||||
}
|
||||
|
||||
// Preprocess filter tickers
|
||||
const filterTickers = filterQuery
|
||||
? filterQuery.split(",").map((ticker) => ticker.trim().toUpperCase())
|
||||
: [];
|
||||
|
||||
// Precompile rules for more efficient filtering
|
||||
const compiledRules = ruleOfList.map(rule => {
|
||||
const ruleName = rule.name.toLowerCase();
|
||||
const ruleValue = convertUnitToValue(rule.value);
|
||||
|
||||
return {
|
||||
...rule,
|
||||
compiledCheck: createRuleCheck(rule, ruleName, ruleValue)
|
||||
};
|
||||
});
|
||||
|
||||
// Optimized filtering with precompiled rules
|
||||
return rawData.filter(item => {
|
||||
// Early ticker filtering
|
||||
if (filterTickers.length > 0 &&
|
||||
!filterTickers.includes(item.ticker.toUpperCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply all precompiled rules
|
||||
return compiledRules.every(rule => rule.compiledCheck(item));
|
||||
});
|
||||
}
|
||||
|
||||
// Centralized rule checking logic
|
||||
function createRuleCheck(rule, ruleName, ruleValue) {
|
||||
// Handle volumeOIRatio
|
||||
if (ruleName === 'volumeoiratio') {
|
||||
const now = new Date(new Date().toLocaleString("en-US", { timeZone: "America/New_York" }));
|
||||
|
||||
|
||||
if (ruleName === 'volumeoiratio') {
|
||||
return (item) => {
|
||||
const volume = parseFloat(item.volume);
|
||||
const openInterest = parseFloat(item.open_interest);
|
||||
@ -190,99 +87,99 @@ function createRuleCheck(rule, ruleName, ruleValue) {
|
||||
};
|
||||
}
|
||||
|
||||
// Handle "between" condition
|
||||
if (rule.condition === 'between') {
|
||||
return (item) => {
|
||||
const itemValue = parseFloat(item[rule.name]);
|
||||
|
||||
// Handle array of ruleValue for between condition
|
||||
if (!Array.isArray(ruleValue)) return true;
|
||||
|
||||
// Convert rule values, ensuring they are valid
|
||||
const [min, max] = ruleValue.map(convertUnitToValue);
|
||||
|
||||
// Handle the case where one or both values are missing (empty string or undefined)
|
||||
if ((min === '' || min === undefined || min === null) &&
|
||||
(max === '' || max === undefined || max === null)) {
|
||||
return true; // If both values are empty or undefined, consider the condition as met (open-ended)
|
||||
}
|
||||
|
||||
// If only one of min or max is missing, handle it as open-ended
|
||||
if (min === '' || min === undefined || min === null) {
|
||||
return itemValue <= max; // If min is missing, only check against max
|
||||
}
|
||||
|
||||
if (max === '' || max === undefined || max === null) {
|
||||
return itemValue >= min; // If max is missing, only check against min
|
||||
}
|
||||
|
||||
// If both min and max are defined, proceed with the normal comparison
|
||||
return itemValue > min && itemValue < max;
|
||||
};
|
||||
}
|
||||
|
||||
// Handle date_expiration
|
||||
if (ruleName === 'date_expiration') {
|
||||
// If 'any' is specified or no specific range is set
|
||||
if (isAny(ruleValue)) return () => true;
|
||||
|
||||
return (item) => {
|
||||
if (Array.isArray(ruleValue)) {
|
||||
return ruleValue.some(range => isDateWithinRange(item[rule.name], range));
|
||||
}
|
||||
return isDateWithinRange(item[rule.name], ruleValue);
|
||||
};
|
||||
// If ruleValue is empty, undefined, "any", or an array containing only "any", return a function that always returns true
|
||||
if (ruleValue === "" || ruleValue === undefined || isAny(ruleValue)) {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
// Categorical data handling
|
||||
const categoricalFields = [
|
||||
'put_call',
|
||||
'sentiment',
|
||||
'execution_estimate',
|
||||
'option_activity_type',
|
||||
'underlying_type'
|
||||
];
|
||||
return (item) => {
|
||||
const expirationDate = new Date(item[rule.name]);
|
||||
if (isNaN(expirationDate)) return false; // Handle invalid dates
|
||||
|
||||
if (categoricalFields.includes(ruleName)) {
|
||||
// If 'any' is specified
|
||||
if (isAny(ruleValue)) return () => true;
|
||||
const daysDiff = (expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);
|
||||
|
||||
if (rule.condition === 'between' && Array.isArray(ruleValue)) {
|
||||
const [minDays, maxDays] = ruleValue.map(val =>
|
||||
val === '' || val === null || val === undefined ? null : parseFloat(val.toString())
|
||||
);
|
||||
|
||||
if (minDays === null && maxDays === null) return true;
|
||||
if (minDays === null) return daysDiff <= maxDays;
|
||||
if (maxDays === null) return daysDiff >= minDays;
|
||||
|
||||
return daysDiff >= minDays && daysDiff <= maxDays;
|
||||
}
|
||||
|
||||
if (rule.condition === 'over' && typeof ruleValue === 'number') {
|
||||
return daysDiff >= ruleValue;
|
||||
}
|
||||
|
||||
if (rule.condition === 'under' && typeof ruleValue === 'number') {
|
||||
return daysDiff <= ruleValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
// Handle string-based conditions (sentiment, option_type, etc.)
|
||||
if (categoricalFields?.includes(ruleName)) {
|
||||
return (item) => {
|
||||
// If ruleValue is empty, undefined, or "any", return true for all items
|
||||
if (ruleValue === "" || ruleValue === undefined || isAny(ruleValue)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const itemValue = item[rule.name];
|
||||
|
||||
// Handle null or undefined
|
||||
if (itemValue === null || itemValue === undefined) return false;
|
||||
|
||||
const lowerItemValue = itemValue.toString().toLowerCase();
|
||||
|
||||
// Handle array of values
|
||||
// Handle array of values for categorical fields
|
||||
if (Array.isArray(ruleValue)) {
|
||||
return ruleValue.some(
|
||||
value => lowerItemValue === value.toString().toLowerCase()
|
||||
// Remove any empty or undefined values from ruleValue
|
||||
const validRuleValues = ruleValue.filter(val => val !== "" && val !== undefined);
|
||||
|
||||
// If no valid values remain, return true for all items
|
||||
if (validRuleValues.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If itemValue is an array, check if any of the values match
|
||||
if (Array.isArray(itemValue)) {
|
||||
return validRuleValues.some(val =>
|
||||
itemValue.some(iv => iv.toLowerCase() === val.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// If itemValue is a string, check if it's in the validRuleValues array
|
||||
return validRuleValues.some(val =>
|
||||
itemValue?.toLowerCase() === val.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Single value comparison
|
||||
return lowerItemValue === ruleValue.toString().toLowerCase();
|
||||
|
||||
// Handle single string value
|
||||
if (typeof ruleValue === 'string') {
|
||||
// If itemValue is an array, check if any value matches
|
||||
if (Array.isArray(itemValue)) {
|
||||
return itemValue.some(iv => iv.toLowerCase() === ruleValue.toLowerCase());
|
||||
}
|
||||
|
||||
// If both are strings, do a direct comparison
|
||||
return itemValue?.toLowerCase() === ruleValue.toLowerCase();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
// Default numeric comparison
|
||||
// Fallback to other numeric conditions
|
||||
return (item) => {
|
||||
const itemValue = item[rule.name];
|
||||
if (typeof ruleValue === 'string') return true; // Handle cases where the rule value is a string but not 'sentiment' or 'option_type'
|
||||
|
||||
// Skip string rule values
|
||||
if (typeof ruleValue === 'string') return true;
|
||||
|
||||
// Handle null or undefined
|
||||
if (itemValue === null || itemValue === undefined) return false;
|
||||
|
||||
const numericItemValue = parseFloat(itemValue);
|
||||
|
||||
// Invalid numeric conversion
|
||||
if (isNaN(numericItemValue)) return false;
|
||||
|
||||
// Comparison conditions
|
||||
if (rule.condition === 'over' && numericItemValue <= ruleValue) return false;
|
||||
if (rule.condition === 'under' && numericItemValue >= ruleValue) return false;
|
||||
|
||||
@ -290,13 +187,47 @@ function createRuleCheck(rule, ruleName, ruleValue) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function filterRawData(rawData, ruleOfList, filterQuery) {
|
||||
// Early return for empty inputs
|
||||
if (!rawData?.length ) {
|
||||
return rawData || [];
|
||||
}
|
||||
|
||||
// Preprocess filter tickers
|
||||
const filterTickers = filterQuery
|
||||
? filterQuery.split(",").map((ticker) => ticker.trim().toUpperCase())
|
||||
: [];
|
||||
|
||||
// Precompile rules for more efficient filtering
|
||||
const compiledRules = ruleOfList.map(rule => {
|
||||
const ruleName = rule?.name?.toLowerCase();
|
||||
const ruleValue = convertUnitToValue(rule.value);
|
||||
|
||||
return {
|
||||
...rule,
|
||||
compiledCheck: createRuleCheck(rule, ruleName, ruleValue)
|
||||
};
|
||||
});
|
||||
|
||||
// Optimized filtering with precompiled rules
|
||||
return rawData?.filter(item => {
|
||||
// Early ticker filtering
|
||||
if (filterTickers?.length > 0 &&
|
||||
!filterTickers?.includes(item.ticker.toUpperCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply all precompiled rules
|
||||
return compiledRules.every(rule => rule?.compiledCheck(item));
|
||||
});
|
||||
}
|
||||
|
||||
// Web Worker message handler
|
||||
onmessage = async (event: MessageEvent) => {
|
||||
const { rawData, ruleOfList, filterQuery } = event.data || {};
|
||||
|
||||
// Filter the data
|
||||
let filteredData = await filterRawData(rawData, ruleOfList, filterQuery);
|
||||
|
||||
// Remove duplicates based on id
|
||||
filteredData = Array.from(
|
||||
new Map(filteredData?.map((item) => [item?.id, item]))?.values()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user