update screener of options flow page
This commit is contained in:
parent
0455cac269
commit
703d30b6ab
@ -44,7 +44,7 @@
|
|||||||
const allRules = {
|
const allRules = {
|
||||||
volume: {
|
volume: {
|
||||||
label: "Volume",
|
label: "Volume",
|
||||||
step: ["100K", "10K", "1K"],
|
step: ["100K", "50K", "20K", "10K", "5K", "2K", "1K", "100", "0"],
|
||||||
defaultCondition: "over",
|
defaultCondition: "over",
|
||||||
defaultValue: "any",
|
defaultValue: "any",
|
||||||
},
|
},
|
||||||
@ -133,7 +133,13 @@
|
|||||||
|
|
||||||
Object.keys(allRules).forEach((ruleName) => {
|
Object.keys(allRules).forEach((ruleName) => {
|
||||||
ruleCondition[ruleName] = allRules[ruleName].defaultCondition;
|
ruleCondition[ruleName] = allRules[ruleName].defaultCondition;
|
||||||
|
|
||||||
|
// Check if the default condition is "between"
|
||||||
|
if (allRules[ruleName].defaultCondition === "between") {
|
||||||
|
valueMappings[ruleName] = allRules[ruleName].defaultValue || [null, null];
|
||||||
|
} else {
|
||||||
valueMappings[ruleName] = allRules[ruleName].defaultValue;
|
valueMappings[ruleName] = allRules[ruleName].defaultValue;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update ruleCondition and valueMappings based on existing rules
|
// Update ruleCondition and valueMappings based on existing rules
|
||||||
@ -277,9 +283,17 @@
|
|||||||
//console.log(displayedData)
|
//console.log(displayedData)
|
||||||
};
|
};
|
||||||
|
|
||||||
function changeRuleCondition(name: string, state: string) {
|
async function changeRuleCondition(name: string, state: string) {
|
||||||
ruleName = name;
|
ruleName = name;
|
||||||
ruleCondition[ruleName] = state;
|
if (
|
||||||
|
ruleCondition[ruleName] === "between" &&
|
||||||
|
["over", "under"]?.includes(state?.toLowerCase())
|
||||||
|
) {
|
||||||
|
valueMappings[ruleName] = "";
|
||||||
|
}
|
||||||
|
ruleCondition[ruleName] = state?.toLowerCase();
|
||||||
|
|
||||||
|
await handleChangeValue(valueMappings[ruleName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkedItems = new Set(ruleOfList.flatMap((rule) => rule.value));
|
let checkedItems = new Set(ruleOfList.flatMap((rule) => rule.value));
|
||||||
@ -288,12 +302,43 @@
|
|||||||
return checkedItems.has(item);
|
return checkedItems.has(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility function to convert values to comparable numbers
|
||||||
|
// Utility function to convert values to comparable numbers
|
||||||
|
function parseValue(val) {
|
||||||
|
if (typeof val === "string") {
|
||||||
|
// Handle percentage values
|
||||||
|
if (val.endsWith("%")) {
|
||||||
|
return parseFloat(val);
|
||||||
|
}
|
||||||
|
// Handle values with suffixes like K (thousand), M (million), B (billion)
|
||||||
|
const suffixMap = {
|
||||||
|
K: 1e3,
|
||||||
|
M: 1e6,
|
||||||
|
B: 1e9,
|
||||||
|
};
|
||||||
|
const suffix = val.slice(-1).toUpperCase();
|
||||||
|
const numberPart = parseFloat(val);
|
||||||
|
if (suffix in suffixMap) {
|
||||||
|
return numberPart * suffixMap[suffix];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parseFloat(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom sorting function
|
||||||
|
function customSort(a, b) {
|
||||||
|
return parseValue(a) - parseValue(b);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleChangeValue(value) {
|
async function handleChangeValue(value) {
|
||||||
|
// Toggle checkedItems logic
|
||||||
if (checkedItems.has(value)) {
|
if (checkedItems.has(value)) {
|
||||||
checkedItems.delete(value);
|
checkedItems.delete(value);
|
||||||
} else {
|
} else {
|
||||||
checkedItems.add(value);
|
checkedItems.add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Specific rule handling for options-related rules
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
"put_call",
|
"put_call",
|
||||||
@ -306,29 +351,37 @@
|
|||||||
) {
|
) {
|
||||||
// Ensure valueMappings[ruleName] is initialized as an array
|
// Ensure valueMappings[ruleName] is initialized as an array
|
||||||
if (!Array.isArray(valueMappings[ruleName])) {
|
if (!Array.isArray(valueMappings[ruleName])) {
|
||||||
valueMappings[ruleName] = []; // Initialize as an empty array if not already
|
valueMappings[ruleName] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Similar logic to the original function for adding/removing values
|
||||||
const index = valueMappings[ruleName].indexOf(value);
|
const index = valueMappings[ruleName].indexOf(value);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
// Add the country if it's not already selected
|
|
||||||
valueMappings[ruleName].push(value);
|
valueMappings[ruleName].push(value);
|
||||||
|
// Sort the array when a new value is added
|
||||||
|
valueMappings[ruleName] = valueMappings[ruleName].sort(customSort);
|
||||||
} else {
|
} else {
|
||||||
// Remove the country if it's already selected (deselect)
|
|
||||||
valueMappings[ruleName].splice(index, 1);
|
valueMappings[ruleName].splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no countries are selected, set value to "any"
|
// Set to "any" if no values are selected
|
||||||
if (valueMappings[ruleName].length === 0) {
|
if (valueMappings[ruleName].length === 0) {
|
||||||
valueMappings[ruleName] = "any";
|
valueMappings[ruleName] = "any";
|
||||||
}
|
}
|
||||||
} else if (ruleName in valueMappings) {
|
} else if (ruleName in valueMappings) {
|
||||||
// Handle non-country rules as single values
|
// For rules that require sorting (like range or numeric values)
|
||||||
|
if (ruleCondition[ruleName] === "between" && Array.isArray(value)) {
|
||||||
|
// Sort the array for between conditions
|
||||||
|
valueMappings[ruleName] = value.sort(customSort);
|
||||||
|
} else {
|
||||||
|
// Handle non-specific rules as single values
|
||||||
valueMappings[ruleName] = value;
|
valueMappings[ruleName] = value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Unhandled rule: ${ruleName}`);
|
console.warn(`Unhandled rule: ${ruleName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update ruleOfList (if applicable)
|
||||||
const ruleToUpdate = ruleOfList?.find((rule) => rule.name === ruleName);
|
const ruleToUpdate = ruleOfList?.find((rule) => rule.name === ruleName);
|
||||||
if (ruleToUpdate) {
|
if (ruleToUpdate) {
|
||||||
ruleToUpdate.value = valueMappings[ruleToUpdate.name];
|
ruleToUpdate.value = valueMappings[ruleToUpdate.name];
|
||||||
@ -336,10 +389,40 @@
|
|||||||
ruleOfList = [...ruleOfList];
|
ruleOfList = [...ruleOfList];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger worker load and save cookie
|
||||||
shouldLoadWorker.set(true);
|
shouldLoadWorker.set(true);
|
||||||
await saveCookieRuleOfList();
|
await saveCookieRuleOfList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function stepSizeValue(value, condition) {
|
||||||
|
const match = value.toString().match(/^(-?[\d.]+)([KMB%]?)$/);
|
||||||
|
if (!match) return value;
|
||||||
|
|
||||||
|
let [_, number, suffix] = match;
|
||||||
|
number = parseFloat(number);
|
||||||
|
|
||||||
|
let step = 1;
|
||||||
|
|
||||||
|
number += condition === "add" ? step : -step;
|
||||||
|
|
||||||
|
// Round to 2 decimal places for consistency
|
||||||
|
number = parseFloat(number?.toFixed(2));
|
||||||
|
const newValue = suffix ? `${number}${suffix}` : Math?.round(number);
|
||||||
|
await handleChangeValue(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleValueInput(event, ruleName, index = null) {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (ruleCondition[ruleName] === "between") {
|
||||||
|
const currentValues = valueMappings[ruleName] || ["", ""];
|
||||||
|
currentValues[index] = newValue;
|
||||||
|
await handleChangeValue(currentValues);
|
||||||
|
} else {
|
||||||
|
await handleChangeValue(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const nyseDate = new Date(
|
const nyseDate = new Date(
|
||||||
data?.getOptionsFlowFeed?.at(0)?.date ?? null,
|
data?.getOptionsFlowFeed?.at(0)?.date ?? null,
|
||||||
)?.toLocaleString("en-US", {
|
)?.toLocaleString("en-US", {
|
||||||
@ -1272,10 +1355,14 @@ function sendMessage(message) {
|
|||||||
<span class="truncate ml-2 text-sm sm:text-[1rem]">
|
<span class="truncate ml-2 text-sm sm:text-[1rem]">
|
||||||
{#if valueMappings[row?.rule] === "any"}
|
{#if valueMappings[row?.rule] === "any"}
|
||||||
Any
|
Any
|
||||||
|
{:else if ruleCondition[row?.rule] === "between"}
|
||||||
|
{Array.isArray(valueMappings[row?.rule])
|
||||||
|
? `${valueMappings[row?.rule][0]}-${valueMappings[row?.rule][1] ?? "Any"}`
|
||||||
|
: "Any"}
|
||||||
{:else}
|
{:else}
|
||||||
{ruleCondition[row?.rule] !== undefined
|
{ruleCondition[row?.rule]
|
||||||
? ruleCondition[row?.rule]
|
?.replace("under", "Under")
|
||||||
: ""}
|
?.replace("over", "Over") ?? ""}
|
||||||
{valueMappings[row?.rule]}
|
{valueMappings[row?.rule]}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
@ -1295,7 +1382,7 @@ function sendMessage(message) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
class="w-56 h-fit max-h-72 overflow-y-auto "
|
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", "date_expiration", "underlying_type"]?.includes(row?.rule)}
|
||||||
<DropdownMenu.Label
|
<DropdownMenu.Label
|
||||||
@ -1304,42 +1391,148 @@ function sendMessage(message) {
|
|||||||
<div
|
<div
|
||||||
class="flex items-center justify-start gap-x-1"
|
class="flex items-center justify-start gap-x-1"
|
||||||
>
|
>
|
||||||
|
<!--Start Dropdown for Condition-->
|
||||||
<div
|
<div
|
||||||
class="relative inline-block flex flex-row items-center justify-center"
|
class="-ml-2 relative inline-block text-left"
|
||||||
>
|
>
|
||||||
<label
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger asChild let:builder
|
||||||
|
><Button
|
||||||
|
builders={[builder]}
|
||||||
|
class="w-fit -mt-1 -ml-2 bg-[#09090B] flex flex-row justify-between items-center text-white"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="truncate ml-2 text-sm sm:text-[1rem]"
|
||||||
|
>
|
||||||
|
{ruleCondition[ruleName]
|
||||||
|
?.replace("under", "Under")
|
||||||
|
?.replace("over", "Over")
|
||||||
|
?.replace("between", "Between")}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="mt-1 -mr-1 ml-1 h-5 w-5 xs:ml-2 !ml-0 sm:ml-0 inline-block"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
style="max-width:40px"
|
||||||
|
aria-hidden="true"
|
||||||
|
><path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content>
|
||||||
|
<DropdownMenu.Group>
|
||||||
|
{#each ["Over", "Under", "Between"] as item}
|
||||||
|
<DropdownMenu.Item
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
changeRuleCondition(row?.rule, "under")}
|
changeRuleCondition(
|
||||||
class="cursor-pointer flex flex-row mr-2 justify-center items-center"
|
row?.rule,
|
||||||
|
item,
|
||||||
|
)}
|
||||||
|
class="cursor-pointer text-[1rem] font-normal"
|
||||||
|
>{item}</DropdownMenu.Item
|
||||||
>
|
>
|
||||||
<input
|
{/each}
|
||||||
type="radio"
|
</DropdownMenu.Group>
|
||||||
class="radio checked:bg-[#fff] bg-[#09090B] border border-gray-600 mr-2"
|
</DropdownMenu.Content>
|
||||||
checked={ruleCondition[row?.rule] ===
|
</DropdownMenu.Root>
|
||||||
"under"}
|
|
||||||
name={row?.rule}
|
|
||||||
/>
|
|
||||||
<span class="label-text text-white"
|
|
||||||
>Under</span
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
on:click={() =>
|
|
||||||
changeRuleCondition(row?.rule, "over")}
|
|
||||||
class="cursor-pointer flex flex-row ml-2 justify-center items-center"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
class="radio checked:bg-[#fff] bg-[#09090B] border border-gray-600 mr-2"
|
|
||||||
checked={ruleCondition[row?.rule] ===
|
|
||||||
"over"}
|
|
||||||
name={row?.rule}
|
|
||||||
/>
|
|
||||||
<span class="label-text text-white"
|
|
||||||
>Over</span
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if ruleCondition[row?.rule] === "between"}
|
||||||
|
<div class="flex gap-x-1 -ml-2 z-10 -mt-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Min"
|
||||||
|
value={Array.isArray(
|
||||||
|
valueMappings[row?.rule],
|
||||||
|
)
|
||||||
|
? (valueMappings[row?.rule][0] ?? "")
|
||||||
|
: ""}
|
||||||
|
on:input={(e) =>
|
||||||
|
handleValueInput(e, row?.rule, 0)}
|
||||||
|
class="ios-zoom-fix block max-w-[3.5rem] rounded-sm placeholder:text-gray-200 font-normal p-1 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500 bg-secondary"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-white text-[1rem] font-normal mt-1"
|
||||||
|
>
|
||||||
|
&
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Max"
|
||||||
|
value={Array.isArray(
|
||||||
|
valueMappings[row?.rule],
|
||||||
|
)
|
||||||
|
? (valueMappings[row?.rule][1] ?? "")
|
||||||
|
: ""}
|
||||||
|
on:input={(e) =>
|
||||||
|
handleValueInput(e, row?.rule, 1)}
|
||||||
|
class="ios-zoom-fix block max-w-[3.5rem] rounded-sm placeholder:text-gray-200 font-normal p-1 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500 bg-secondary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Value"
|
||||||
|
value={valueMappings[row?.rule] === "any"
|
||||||
|
? ""
|
||||||
|
: valueMappings[row?.rule]}
|
||||||
|
on:input={(e) =>
|
||||||
|
handleValueInput(e, row?.rule)}
|
||||||
|
class="ios-zoom-fix block max-w-[4.8rem] rounded-sm placeholder:text-gray-200 font-normal p-1 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500 bg-secondary"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if ["over", "under"]?.includes(ruleCondition[ruleName]?.toLowerCase())}
|
||||||
|
<div
|
||||||
|
class="ml-2 flex touch-manipulation flex-row items-center gap-x-1.5"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() =>
|
||||||
|
stepSizeValue(
|
||||||
|
valueMappings[row?.rule],
|
||||||
|
"add",
|
||||||
|
)}
|
||||||
|
><svg
|
||||||
|
class="size-6 cursor-pointer text-white"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
style="max-width:40px"
|
||||||
|
><path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
></path></svg
|
||||||
|
></button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() =>
|
||||||
|
stepSizeValue(
|
||||||
|
valueMappings[row?.rule],
|
||||||
|
"minus",
|
||||||
|
)}
|
||||||
|
><svg
|
||||||
|
class="size-6 cursor-pointer text-white"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
style="max-width:40px"
|
||||||
|
><path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
></path></svg
|
||||||
|
></button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<!--End Dropdown for Condition-->
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenu.Label>
|
</DropdownMenu.Label>
|
||||||
{:else}
|
{:else}
|
||||||
@ -1352,22 +1545,48 @@ function sendMessage(message) {
|
|||||||
{/if}
|
{/if}
|
||||||
<DropdownMenu.Group class="min-h-10 mt-2">
|
<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", "date_expiration", "underlying_type"]?.includes(row?.rule)}
|
||||||
{#each row?.step as newValue}
|
{#each row?.step as newValue, index}
|
||||||
|
{#if ruleCondition[row?.rule] === "between"}
|
||||||
|
{#if newValue && row?.step[index + 1]}
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
class="sm:hover:bg-[#2A2E39]"
|
class="sm:hover:bg-primary"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
handleChangeValue([
|
||||||
|
row?.step[index],
|
||||||
|
row?.step[index + 1],
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
class="block w-full border-b border-gray-600 px-4 py-1.5 text-left text-sm sm:text-[1rem] rounded text-white last:border-0 sm:hover:bg-primary focus:bg-blue-100 focus:text-gray-900 focus:outline-none"
|
||||||
|
>
|
||||||
|
{ruleCondition[row?.rule]?.replace(
|
||||||
|
"between",
|
||||||
|
"Between",
|
||||||
|
)}
|
||||||
|
{row?.step[index + 1]} - {row?.step[
|
||||||
|
index
|
||||||
|
]}
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
class="sm:hover:bg-primary"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
handleChangeValue(newValue);
|
handleChangeValue(newValue);
|
||||||
}}
|
}}
|
||||||
class="block w-full border-b border-gray-600 px-4 py-1.5 text-left text-sm sm:text-[1rem] rounded text-white last:border-0 sm:hover:bg-[#2A2E39] focus:bg-blue-100 focus:text-gray-900 focus:outline-none"
|
class="block w-full border-b border-gray-600 px-4 py-1.5 text-left text-sm sm:text-[1rem] rounded text-white last:border-0 sm:hover:bg-primary focus:bg-blue-100 focus:text-gray-900 focus:outline-none"
|
||||||
>
|
>
|
||||||
{ruleCondition[row?.rule] !== undefined
|
{ruleCondition[row?.rule]
|
||||||
? ruleCondition[row?.rule]
|
?.replace("under", "Under")
|
||||||
: ""}
|
?.replace("over", "Over")}
|
||||||
{newValue}
|
{newValue}
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/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", "date_expiration", "underlying_type"]?.includes(row?.rule)}
|
||||||
{#each row?.step as item}
|
{#each row?.step as item}
|
||||||
|
|||||||
@ -136,96 +136,172 @@ function isDateWithinRange(dateString: string, range: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function filterRawData(rawData, ruleOfList, filterQuery) {
|
async function filterRawData(rawData, ruleOfList, filterQuery) {
|
||||||
// Split filterQuery into an array of tickers if it's a comma-separated string
|
// Early return for empty inputs
|
||||||
|
if (!rawData?.length || !ruleOfList?.length) {
|
||||||
|
return rawData || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preprocess filter tickers
|
||||||
const filterTickers = filterQuery
|
const filterTickers = filterQuery
|
||||||
? filterQuery.split(",").map((ticker) => ticker.trim().toUpperCase())
|
? filterQuery.split(",").map((ticker) => ticker.trim().toUpperCase())
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return rawData?.filter((item) => {
|
// Precompile rules for more efficient filtering
|
||||||
// Check if the item's ticker matches any of the tickers in filterTickers
|
const compiledRules = ruleOfList.map(rule => {
|
||||||
if (
|
|
||||||
filterTickers.length > 0 &&
|
|
||||||
!filterTickers.includes(item.ticker.toUpperCase())
|
|
||||||
) {
|
|
||||||
return false; // Exclude if the ticker doesn't match any in filterTickers
|
|
||||||
}
|
|
||||||
|
|
||||||
return ruleOfList.every((rule) => {
|
|
||||||
const ruleName = rule.name.toLowerCase();
|
const ruleName = rule.name.toLowerCase();
|
||||||
const ruleValue = convertUnitToValue(rule.value);
|
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
|
// Handle volumeOIRatio
|
||||||
if (ruleName === "volumeoiratio") {
|
if (ruleName === 'volumeoiratio') {
|
||||||
|
return (item) => {
|
||||||
const volume = parseFloat(item.volume);
|
const volume = parseFloat(item.volume);
|
||||||
const openInterest = parseFloat(item.open_interest);
|
const openInterest = parseFloat(item.open_interest);
|
||||||
|
|
||||||
if (isNaN(volume) || isNaN(openInterest) || openInterest === 0) {
|
if (isNaN(volume) || isNaN(openInterest) || openInterest === 0) {
|
||||||
return false; // Invalid data, exclude this item
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = (volume / openInterest) * 100;
|
const ratio = (volume / openInterest) * 100;
|
||||||
|
|
||||||
if (rule.condition === "over" && ratio <= ruleValue) return false;
|
if (rule.condition === 'over' && ratio <= ruleValue) return false;
|
||||||
if (rule.condition === "under" && ratio >= ruleValue) return false;
|
if (rule.condition === 'under' && ratio >= ruleValue) return false;
|
||||||
return true;
|
return true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemValue = item[rule.name];
|
// 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
|
// Handle date_expiration
|
||||||
if (ruleName === "date_expiration") {
|
if (ruleName === 'date_expiration') {
|
||||||
if (isAny(ruleValue)) return true;
|
// If 'any' is specified or no specific range is set
|
||||||
|
if (isAny(ruleValue)) return () => true;
|
||||||
|
|
||||||
|
return (item) => {
|
||||||
if (Array.isArray(ruleValue)) {
|
if (Array.isArray(ruleValue)) {
|
||||||
return ruleValue.some((range) => isDateWithinRange(itemValue, range));
|
return ruleValue.some(range => isDateWithinRange(item[rule.name], range));
|
||||||
}
|
}
|
||||||
return isDateWithinRange(itemValue, ruleValue as string);
|
return isDateWithinRange(item[rule.name], ruleValue);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle categorical data
|
// Categorical data handling
|
||||||
else if (
|
const categoricalFields = [
|
||||||
[
|
'put_call',
|
||||||
"put_call",
|
'sentiment',
|
||||||
"sentiment",
|
'execution_estimate',
|
||||||
"execution_estimate",
|
'option_activity_type',
|
||||||
"option_activity_type",
|
'underlying_type'
|
||||||
"underlying_type",
|
];
|
||||||
].includes(ruleName)
|
|
||||||
) {
|
if (categoricalFields.includes(ruleName)) {
|
||||||
if (isAny(ruleValue)) return true;
|
// If 'any' is specified
|
||||||
|
if (isAny(ruleValue)) return () => true;
|
||||||
|
|
||||||
|
return (item) => {
|
||||||
|
const itemValue = item[rule.name];
|
||||||
|
|
||||||
|
// Handle null or undefined
|
||||||
if (itemValue === null || itemValue === undefined) return false;
|
if (itemValue === null || itemValue === undefined) return false;
|
||||||
|
|
||||||
const lowerItemValue = itemValue.toString().toLowerCase();
|
const lowerItemValue = itemValue.toString().toLowerCase();
|
||||||
|
|
||||||
|
// Handle array of values
|
||||||
if (Array.isArray(ruleValue)) {
|
if (Array.isArray(ruleValue)) {
|
||||||
return ruleValue.some(
|
return ruleValue.some(
|
||||||
(value) => lowerItemValue === value.toString().toLowerCase(),
|
value => lowerItemValue === value.toString().toLowerCase()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single value comparison
|
||||||
return lowerItemValue === ruleValue.toString().toLowerCase();
|
return lowerItemValue === ruleValue.toString().toLowerCase();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default numeric or string comparison
|
// Default numeric comparison
|
||||||
if (typeof ruleValue === "string") return true;
|
return (item) => {
|
||||||
|
const itemValue = item[rule.name];
|
||||||
|
|
||||||
|
// Skip string rule values
|
||||||
|
if (typeof ruleValue === 'string') return true;
|
||||||
|
|
||||||
|
// Handle null or undefined
|
||||||
if (itemValue === null || itemValue === undefined) return false;
|
if (itemValue === null || itemValue === undefined) return false;
|
||||||
|
|
||||||
const numericItemValue = parseFloat(itemValue);
|
const numericItemValue = parseFloat(itemValue);
|
||||||
|
|
||||||
|
// Invalid numeric conversion
|
||||||
if (isNaN(numericItemValue)) return false;
|
if (isNaN(numericItemValue)) return false;
|
||||||
|
|
||||||
if (rule.condition === "over" && numericItemValue <= ruleValue)
|
// Comparison conditions
|
||||||
return false;
|
if (rule.condition === 'over' && numericItemValue <= ruleValue) return false;
|
||||||
if (rule.condition === "under" && numericItemValue >= ruleValue)
|
if (rule.condition === 'under' && numericItemValue >= ruleValue) return false;
|
||||||
return false;
|
|
||||||
return true;
|
return true;
|
||||||
});
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web Worker message handler
|
||||||
onmessage = async (event: MessageEvent) => {
|
onmessage = async (event: MessageEvent) => {
|
||||||
const { rawData, ruleOfList, filterQuery } = event.data || {};
|
const { rawData, ruleOfList, filterQuery } = event.data || {};
|
||||||
|
|
||||||
|
// Filter the data
|
||||||
let filteredData = await filterRawData(rawData, ruleOfList, filterQuery);
|
let filteredData = await filterRawData(rawData, ruleOfList, filterQuery);
|
||||||
filteredData = Array?.from(
|
|
||||||
new Map(filteredData?.map((item) => [item?.id, item]))?.values(),
|
// Remove duplicates based on id
|
||||||
|
filteredData = Array.from(
|
||||||
|
new Map(filteredData?.map((item) => [item?.id, item]))?.values()
|
||||||
);
|
);
|
||||||
|
|
||||||
postMessage({ message: "success", filteredData });
|
postMessage({ message: "success", filteredData });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2814,7 +2814,7 @@ const handleKeyDown = (event) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
class="w-64 min-h-64 max-h-72 overflow-y-auto scroller"
|
class="w-64 min-h-auto max-h-72 overflow-y-auto scroller"
|
||||||
>
|
>
|
||||||
{#if !["sma20", "sma50", "sma100", "sma200", "ema20", "ema50", "ema100", "ema200", "grahamNumber", "analystRating", "halalStocks", "score", "sector", "industry", "country"]?.includes(row?.rule)}
|
{#if !["sma20", "sma50", "sma100", "sma200", "ema20", "ema50", "ema100", "ema200", "grahamNumber", "analystRating", "halalStocks", "score", "sector", "industry", "country"]?.includes(row?.rule)}
|
||||||
<DropdownMenu.Label
|
<DropdownMenu.Label
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user